LESS CSS retina media queries without redundancy

2019-01-29 07:39发布

I am using a component-based CSS style, so I have been using mixins to allow me to use media queries without accidentally compiling hundreds of them. This is what I am doing for screen sizes:

Main file:

.mq-medium() {}

@import //other files

@media only screen and (min-width: 600px) {
  .mq-medium;
}

Another file:

.mq-medium() {
  .banner {
    width: 50%;
  }
}

This can be used several times and results in grouped queries.

The Problem

I am trying to do the same thing for retina background-image queries, and am having trouble figuring out how to do so. This is my test:

.mq-retina() { }

.background-image(@image){
  @filename:  ~`/(.*)\.(jpg|jpeg|png|gif)/.exec(@{image})[1]`;
  @extension: ~`/(.*)\.(jpg|jpeg|png|gif)/.exec(@{image})[2]`;
  background-image: ~`"url(@{filename}.@{extension})"`;
  .mq-retina() {
    & {
      background-image: ~`"url(@{filename}_2x.@{extension})"`;
      background-size: 100%;
    }
  }
}

.lol {
  .background-image("test.jpg");
}

@media only screen and (min-device-pixel-ratio: 1.5) { //shortened for this example
  .mq-retina;
}

But the output is just

.lol {
  background-image: url(test.jpg);
}

I believe this has to do with scoping issues, but am unsure how to resolve this. How can I add to the .mq-retina() mixin without scoping problems?

1条回答
孤傲高冷的网名
2楼-- · 2019-01-29 08:09

(See comments above for context of this solution). I'd say that a price for non-repeating media queries will always be "repeating something else then". I.e. it either has to be the media depended property as in:

// ...................................
// usage:

.mq-default() {
    .banner {
        .background-image("test.jpg");
    }
}

.mq-retina() {
    .banner {
        .background-image("test.jpg");
    }
}

// ...................................
// impl:

.mq-default() {}
.mq-retina()  {}

& {
    .mq-default;

    .background-image(@image) {
        background-image: @image;
    }
}

@media (min-device-pixel-ratio: 1.5) {
    .mq-retina;

    .background-image(@image) {
        background-image: replace(@image, "\.", "_2x.");
        background-size: 100%;
    }
}

Or the media depended selector itself, as in:

// ...................................
// usage:

.background-image(banner, "test.jpg");

// ...................................
// impl:

.mq-retina() {}

@media (min-device-pixel-ratio: 1.5) {
    .mq-retina;
}

.background-image(@class, @image) {
    .@{class} {
        background-image: @image;
    }

    .mq-retina() {
        .@{class} {
            background-image: replace(@image, "\.", "_2x.");
            background-size: 100%;
        }
    }
}

---

P.S. For this simplified case it is also possible to modify the first example to get rid of repetitions, for example like this:

// ...................................
// usage:

.mq-common() {
    .banner {
        .background-image("test.jpg");
    }
}

// ...................................
// impl:

.mq-default() {.mq-common}
.mq-retina()  {.mq-common}

& {
    .mq-default;

    .background-image(@image) {
        background-image: @image;
    }
}

@media (min-device-pixel-ratio: 1.5) {
    .mq-retina;

    .background-image(@image) {
        background-image: replace(@image, "\.", "_2x.");
        background-size: 100%;
    }
}

But that way it actually becomes a variant of the second example (where more complex code will lead to repeated selectors in the generated CSS because you won't want to put all properties into .mq-common), not counting that the whole thing also turns to be quite head-scratching.

---

P.P.S. And at last, it is finally possible to consolidate "everything" (in the generated CSS) by introducing another level of indirection, but the source code itself becomes too verbose to be actually usable in practice. (In this example I'll break it into two files for a bit more clear code, but that's not really required - the imported file can be written as one big mixin):

// ...................................
// styles.less:

.banner {
    .mq-default({
        color: red;
    });

    .mq-medium({
        color: green;
    });

    .mq-retina({
        color: blue;
    });

    .background-image("test.jpg");
    note: not "wrapped" properties will appear in every media block;
}

.background-image(@image) {
    .mq-default({
        background-image: @image;
    });

    .mq-retina({
        background-image: replace(@image, "\.", "_2x.");
        background-size: 100%;
    });
}

// ...................................
// main.less:

.media-import(default);

@media (min-width: 600px) {
    .media-import(medium);
}

@media (min-device-pixel-ratio: 1.5) {
    .media-import(retina);
}

.media-import(@device) {
    .mq-default(@styles) when (@device = default) {@styles();}
    .mq-medium(@styles)  when (@device = medium)  {@styles();}
    .mq-retina(@styles)  when (@device = retina)  {@styles();}
    @import (multiple) "styles.less";
}
查看更多
登录 后发表回答