Dynamic classnames with parameters in Less mixin

2019-07-11 07:45发布

I'm trying to create a Less mixin to generate media queries. The goal is to store my breakpoints in a variables.less file, and loop through them to create @media blocks.

The mixin would then be used as:

.mq-medium({
  // rules
});

and generate CSS like:

@media only screen and (min-width: 640px) {
  // rules
}

Here's my current mixin:

variables.less

/* media queries */
@breakpoints: small 0, medium 640px, large 1024px, xlarge 1281px, xxlarge 1440px;

mediaqueries.less

.createMQClasses(@iterator:1) when(@iterator <= length(@breakpoints)-1) {
  @breakpoint: extract(extract(@breakpoints, @iterator),1);
  @breakpoint-next: extract(@breakpoints, (@iterator + 1));
  @breakpoint-next-px: extract(@breakpoint-next, 2);
    .mq-@{breakpoint} {
      @media only screen and (min-width: extract(extract(@breakpoints, @iterator),2)) {
      }
    }

    .createMQClasses((@iterator + 1));
}

.createMQClasses();

So far my code loops through and generates empty @media blocks. However, I need to pass any @rules through to the output. I've done this with static classnames previously, like this:

.mq-medium(@rules) {
  @media only screen and (min-width: extract(extract(@breakpoints, 2),2)) {
    @rules();
  }
}

And that works fine.

But with the dynamic name it's causing errors. I've tried adding an additional parameter to the .mq-@{breakpoint} statements, like:

.mq-@{breakpoint}(@rules) {
  @media only screen and (min-width: extract(extract(@breakpoints, @iterator),2)) {
    @rules();
  }
}

This results in various errors. How can pass the included rules through so they're included in the mixin's output?

1条回答
神经病院院长
2楼-- · 2019-07-11 07:51

No, you can't use variables in parametric mixin names. Thus you can't "generate" mixins by a list of identifiers. So you need to invent some other approach...

Well, there're several possibilities - for example notice that @{breakpoint} part of the mixin name is nothing but just another mixin parameter. Then it could be coded as something like (simplified):

// usage:

@devices: small 0, medium 640px, large 1024px;

.mq(medium, {
    foo {
        bar: baz;
    }
});  

// impl.:

.mq(@id, @style) {
    .-(length(@devices));
    .-(@i) when (@i > 0) {
        .-(@i - 1);
        .--(extract(@devices, @i));
    }
    .--(@device) when (@id = extract(@device, 1)) {
        @media (min-width: extract(@device, 2)) {@style();}
    }
}

---

Alternatively (just to illustrate those "several possibilities"), it is actually possible to make the device id to be a prefix part of the name via certain tricks&hacks. E.g.:

// impl.:
// (here mixin definitions should always go before their usage)

.make-media-mixins();
.make-media-mixins(@i: length(@devices)) when (@i > 0) {
    .make-media-mixins(@i - 1);
    @device: extract(@devices, @i);
    @id: extract(@device, 1);
    .@{id} {.mq(@rules) {
        @media (min-width: extract(@device, 2)) {@rules();}
    }}
}

// usage:

@devices: small 0, medium 640px, large 1024px;

.medium.mq({
    foo {
        bar: baz;
    }
});  

This trick abuses the fact that variables can be used as part of a ruleset name (and then we can use it as a non-parametric namespace prefix), but I would never recommend this code for a real project (it's just too hacky and flawed by any means).

查看更多
登录 后发表回答