Fancy Media Queries with Some Less Magic

Fancy Media Queries with some LESS Magic

Here is what I've done in my projects:

@desktop:   ~"only screen and (min-width: 960px) and (max-width: 1199px)";
@tablet: ~"only screen and (min-width: 720px) and (max-width: 959px)";

@media @desktop {
footer {
width: 940px;
}
}

@media @tablet {
footer {
width: 768px;
}
}

This allows you to only define your media queries once and you can use it throughout your less files. Also a little easier to read. :)

Media Query grouping instead of multiple scattered media queries that match

First, your solution given in the question certainly has some usefulness to it.

One thing I thought, however, was that it would be nice to define all the media query variables "near" one another (your solution would have them under each media query call). So I propose the following as an alternative solution. It also has drawbacks, one being perhaps a bit more coding up front.

LESS Code

//define our break points as variables
@mediaBreak1: 800px;
@mediaBreak2: 1024px;
@mediaBreak3: 1280px;

//this mixin builds the entire media query based on the break number
.buildMediaQuery(@min) {
@media only screen and (min-width: @min) {

//define a variable output mixin for a class included in the query
.myClass1(@color) {
.myClass1 {
color: @color;
}
}

//define a builder guarded mixin for each break point of the query
//in these is where we change the variable for the media break (here, color)
.buildMyClass1() when (@min = @mediaBreak1) {
.myClass1(red);
}
.buildMyClass1() when (@min = @mediaBreak2) {
.myClass1(green);
}
.buildMyClass1() when (@min = @mediaBreak3) {
.myClass1(blue);
}

//call the builder mixin
.buildMyClass1();

//define a variable output mixin for a nested selector included in the query
.mySelector1(@fontSize) {
section {
width: (@min - 40);
margin: 0 auto;
a {
font-size: @fontSize;
}
}
}

//Again, define a builder guarded mixin for each break point of the query
//in these is where we change the variable for the media break (here, font-size)
.buildMySelector1() when (@min = @mediaBreak1) {
.mySelector1(10px);
}
.buildMySelector1() when (@min = @mediaBreak2) {
.mySelector1(12px);
}
.buildMySelector1() when (@min = @mediaBreak3) {
.mySelector1(14px);
}

//call the builder mixin
.buildMySelector1();

//ect., ect., etc. for as many parts needed in the media queries.
}
}

//call our code to build the queries
.buildMediaQuery(@mediaBreak1);
.buildMediaQuery(@mediaBreak2);
.buildMediaQuery(@mediaBreak3);

CSS Output

@media only screen and (min-width: 800px) {
.myClass1 {
color: #ff0000;
}
section {
width: 760px;
margin: 0 auto;
}
section a {
font-size: 10px;
}
}
@media only screen and (min-width: 1024px) {
.myClass1 {
color: #008000;
}
section {
width: 984px;
margin: 0 auto;
}
section a {
font-size: 12px;
}
}
@media only screen and (min-width: 1280px) {
.myClass1 {
color: #0000ff;
}
section {
width: 1240px;
margin: 0 auto;
}
section a {
font-size: 14px;
}
}

LESS function that builds a media query

The main problem of your first snippet is that you can't use mixin call to set an identifier for a {...} block. In the snippet the following:

.media(100px, 400px) {
color: red;
}

is actually a new mixin definition and not really a previously defined .media mixin call (so it simply outputs nothing since this new mixin is never invoked).
And proper mixin call syntax:

.media(100px, 400px); {
color: red;
}

in such context would be an equivalent to:

@query: ~"@media (min-width: 100px) and (max-width: 400px)"; {
color: red;
}

which of course does not make any sense for Less at all and it would throw a error.

-------

Your second snippet is more correct, but yes, since both mixin calls share the same scope there's only one @query variable. It's possible to isolate them by putting each into unnamed namespace (which is simply a ruleset with & name so it creates a new scope but then is output as part of the outer ruleset):

.class {
& {.media(100px, 400px);
@media @query {
color: red;
}}

& {.media(401px, 800px);
@media @query {
color: green;
}}
}

This does the trick but obviously it does not look like something really useful (too verbose and unreadable) so for the sake of reference it would make sense to mention other approaches:

-------

Today, the most clean solution for the particular case would be to use ruleset as mixin parameter:

.media(@min, @max, @styles) {
@media (min-width: @min)
and (max-width: @max) {
@styles();
}
}

.class {
.media(100px, 400px, {
color: red;
});

.media(401px, 800px, {
color: green;
});
}

Though I doubt that in a practical project you'd want to explicitly repeat pixel values every time you need the corresponding media so most likely eventually you end with more semantic mixins, e.g.:

.media(@min, @max, @styles) {
@media (min-width: @min)
and (max-width: @max) {
@styles();
}
}

.tiny-screen(@styles) {.media(100px, 400px, @styles)}
.not-so-tiny-screen(@styles) {.media(401px, 800px, @styles)}

.class {
.tiny-screen({
color: red;
});

.not-so-tiny-screen({
color: green;
});
}

------

Passing rulesets to mixins is not the only method to achieve the goal, there're other methods with various pros and cons (some of those can look even more readable if you go the "semantic media blocks" way). See for example https://stackoverflow.com/a/15842048/2712740 (obviously search for [less] media here at SO will point to more inspirations).

Using class set in media query as mixin in less

Your problem is a common misconception. LESS does not process the @media query, the browser does after LESS has done its work. LESS can only create the CSS code that the browser is going to read. So the @media is "meaningless" to LESS, it is just like any other selector (.someClass div table, etc.), it only processes what the @media is going to serve to the browser.

So that means you need to put all your code that changes for the @media in the @media block. But you also don't want a bunch of repeated code. So instead, create a master mixin to set your @media code, and then call that mixin from the media queries:

.makeFooGroup(@w1, @w2) {
.foo {width: @w1}
.foo-2{width: @w2}
.bar{.foo}
.bar-2{.foo}
.bar-3{.foo-2}
}

@media (min-width: 1000px) {
.makeFooGroup(120px, 150px);
}
@media (max-width: 999px) {
.makeFooGroup(110px, 120px);
}

Produces this css:

@media (min-width: 1000px) {
.foo {width: 120px;}
.foo-2 {width: 150px;}
.bar {width: 120px;}
.bar-2 {width: 120px;}
.bar-3 {width: 150px;}
}
@media (max-width: 999px) {
.foo {width: 110px;}
.foo-2 {width: 120px;}
.bar {width: 110px;}
.bar-2 {width: 110px;}
.bar-3 {width: 120px;}
}

For some further info I've given on LESS and @media related to this, see:

  1. CSS pre-processor with a possibility to define variables in a @media query
  2. Media Query grouping instead of multiple scattered media queries that match

Question on CSS Media Query

Have you tried something like

@media only screen and (device-width: 768px) {
/* desktop styles here */
}

So, lose the not as that is what's making the rule apply to everything but the screen i.e. the desktop in your case.



Related Topics



Leave a reply



Submit