by Martin Vézina | 2013-05-16

In some cases, the media queries that we choose to set have nothing to do with which devices we target. Some media queries are rather set depending on properties of the design.

In a previous post, we talked about how it is possible to use LESS to easily build a responsive stylesheet, but we left it at simple cases where the targeted devices govern the sizes for which responsive styles are set. Now that may seem like the obvious thing to do; after all, why should anything else than the device's size influence the way styles are coded? I mean, when you decide to support iPads, you target a screen size of 1024 X 768 and 768 X 1024, right?

Fixed-width grid

Using back our example, we had a grid of thumbnails centered in a container, whose width depended on how many thumbnails wide it was possible to fit inside the container. The grid was constrained inside a parent element. We'll use this same example, but instead of constraining the grid, we'll put it at the root level of the body, so its only constrain will be the screen width.

Here is typical markup:

<body> <div class="thumbnailWrapper"> <div class="thumbnailContainer"> <div class="thumbnail"> </div> <div class="thumbnail"> </div> <div class="thumbnail"> </div> <div class="thumbnail"> </div> <div class="thumbnail"> </div> <div class="thumbnail"> </div> <div class="thumbnail"> </div> <div class="thumbnail"> </div> <div class="thumbnail"> </div> <div class="thumbnail"> </div> <div class="thumbnail"> </div> </div> </div> </body>

As you know, it's quite easy to center an element in a page : just set its side margins to auto. But in order for that to work, the element must also have a fixed width. In our case, the width of the container depends on how many columns of thumbnails we wish to display : its width has to be the total width of its columns. Let's do that, assuming that we want to display 4 columns of thumbnails:

@thumbSize : 150px; @thumbGutter : 8px; @thumbRealW : @thumbSize + @thumbGutter; .thumbnailWrapper{ margin: @gutter auto; background-color: #ffa; .clearfix(); width: (@thumbRealW * 4) - @thumbGutter; .thumbnailContainer{ margin-left: -@thumbGutter; width: @thumbRealW * 4; } } .thumbnail{ width: @thumbSize; height: @thumbSize; float:left; .border-radius(50%); margin:0 0 @gutter @thumbGutter; background-color: #aac; }

A responsive grid that depends on preset media queries

Simple with less, as we don't have to hardcode the total width of columns. We can calculate it!

To make that grid responsive, we would just need to leave the width of the containers undefined until they are set inside media queries, so as to adapt to each screen size. But in order to do that, we would need to calculate the number of columns that fit inside the screen size, for each media query that we set. That is precisely what we did in our previous post about responsive css. Just pass the available width to a mixin that will calculate the number of thumnbails that fit in it.

@media (min-width: 1200px) { #responsiveThumbnails > .set(1200px); } //other media queries... #responsiveThumbnails{ .set(@availableW) { @maxThumbsWide : floor((@availableW + @thumbGutter) / @thumbRealW); @calculatedW : @maxThumbsWide * @thumbRealW; .thumbnailWrapper{ width:@calculatedW - @thumbGutter; .thumbnailContainer{ width:@calculatedW ; } } } }

Doing it the other way around

As you may have already noticed, the width of the container can exist only as multiples of the thumbnail's width. There is no container that will ever be 3.5 thumbnails wide, it will always be 1, 2, 3, 4... thumbnails wide. These exact widths are intrinsic to the thumbnail's grid, and don't depend on anything else. In other words, the number of thumbnails that fit in a screen depend on the screen width, but the width of the container in turn solely depends on the thumbnail width.

What that means is that we have a list of breakpoints for our grid, that is predetermined and does not depend on popular screen sizes. Even if an iPad is 1024px wide, if a thumbnail is 210px wide, only 4 thumnbails fit in an iPad, so the container will have a width of 840px. We won't bother creating a media query for 1024px, we will create one for the width of 4 thumnbails, which is 840px, and then one for 5 thumbnails, but also one for 3, and so on.

@media (max-width: 419px){ //one thumnbail wide grid } @media (max-width: 629px ) and (min-width: 420px){ //two thumnbails wide grid } @media (max-width: 630px ) and (min-width: 839px){ //three thumnbails wide grid } //etc.

As you can see from the example above, that may make up a lot of media queries, and we need to manually set them depending on our basic thumbnail width. If it changes, we will need to manually change the media queries as well... a tedious task that a computer should be able to do.

The good news is, it is possible to have less variables in media queries conditions, so we could have less compute it inside a loop, and thus set all the media queries that we need for a given thumbnail size. Let's do it!

Loops in less

There is no built-in syntax to make a loop in less, but it is nonetheless possible to make one through the use of recursion. We'll just call a mixin that will call itself. As you may recall, less uses guards instead of ifs for conditions, so we'll just have a second mixin that will have guard to exit the loop. It looks like that, this one with 12 iterations :

#responsiveThumbnails > .setMedia(12); #responsiveThumbnails{ .setMedia(@i) when (@i > 1){ //do stuff //calls itself, decrementing the variable #responsiveThumbnails > .setMedia(@i - 1); } .setMedia(@i) when (@i = 1) { //this one ends the loop } }

In our case, we need three types of media queries :

when the screen is larger than some value, for our largest grid

when the screen width is between two values

when the screen width is smaller than some value, to have our single column design

That means that we'll need three mixins, one for each of these cases. The first one will adress our largest-case scenario, and will then call the second one. The second one will manage all the possible sizes in between, down to the width of two columns. The final one will adress the case when there's only one thumbnail fitting in the screen, and will stop the loop. We will differentiate our first mixin by giving it a different name (with an s appended) and it will be our entry point for the loop. We will pass the largest number of thumnbails we wish to accomodate, and will set it to a reasonable number. There is no need to plan for 10000px wide screens... Here it is, without any media queries yet:

#responsiveThumbnails > .setMedias(12); #responsiveThumbnails{ .setMedias(@i) { //do stuff //calls the in-between mixin, decrementing the variable #responsiveThumbnails > .setMedia(@i - 1); } .setMedia(@i) when (@i > 1){ //do stuff //calls itself, decrementing the variable #responsiveThumbnails > .setMedia(@i - 1); } .setMedia(@i) when (@i = 1) { //this one ends the loop } }

Calculating the widths

The three mixins used in our loop will only set media queries. They won't set directly the width of the container. For that, we will use the mixin we already created, and that still does the job. Even if it could use an already-calculated number for the width, we will leave it as it was before, as it can also be used in contexts when the width passed to it is not a multiple of our thumnbail width. Better have more flexible code.

.set(@availableW) { @maxThumbsWide : floor((@availableW + @thumbGutter) / @thumbRealW); @calculatedW : @maxThumbsWide * @thumbRealW; .thumbnailWrapper{ width:@calculatedW - @thumbGutter; .thumbnailContainer{ width:@calculatedW ; } } }

Now is time to calculate the breakpoints for our media queries. The first mixin is easy, as it is when the screen accomodates at least our largest number of thumnbails. It is therefore our initial increment multiplied with the width of a thumbnail. When we have our number, we will simply create a media query with it, and inside call our dimension setting mixin from above:

.setMedias(@i) { body{ @minW : (@i * @thumbRealW); @media (min-width: @minW){ #responsiveThumbnails > .set(@minW); } } #responsiveThumbnails > .setMedia(@i - 1); }

Our middle-loop mixin has to have a lower and upper limit for its media queries. The lower limit will be the minimal screen dimension that is needed to accomodate for a given number (@i) of thumnbails. The upper limit will be the dimension at which one more thumbnail fits in, minus one pixel.

.setMedia(@i) when (@i > 1) { body{ @minW : (@i * @thumbRealW); @maxW : (((@i + 1) * @thumbRealW) - 1); @media (max-width: @maxW ) and (min-width: @minW){ #responsiveThumbnails > .set(@minW); } } #responsiveThumbnails > .setMedia(@i - 1); }

And finally, our last mixin, the one that will end the loop, will address the case when only one thumbnail, but not two, fits in our screen width.

.setMedia(@i) when (@i = 1) { body{ @maxW : ((2 * @thumbRealW) - 1); @media (max-width: @maxW ){ #responsiveThumbnails > .set(@thumbRealW); } } }

A word about scope

Note that, in the mixins above, the variables and thus the media queries are wrapped inside body elements. The reason is that mixins do not create scope, as absurd as it may seem (to me anyway). Less manual says that "this is contentious", to quote them, so that behavior might change in future versions, but for now we must deal with it. If we don't scope our variables, they will be global and there will be only one value for each, no matter how many loop iterations are performed.

The only way we had to create a scope for our variables was to wrap them in a selector, body{...} in this case. It may seem counter-intuitive to have @media queries inside selectors, but it works thanks to another nice feature of less, namely nested media queries. They were designed to be used in cases that are not the problem we are facing now, but their syntax permits media queries to be nested inside selectors. That feature makes it possible to force a scope inside our mixins and at the same time have media queries in them.

The code

You can see the code for this example on github at https://github.com/mgvez/responsiveless

EDIT

2013-07-31 I added parenthesis around calculations of media dimensions, to comply with less's strict math option.