Less - parametric mixin with shorthand numeric and or string values, with rem and px fallback

谁说我不能喝 提交于 2019-12-11 18:49:48

问题


Using Less, I'm trying output css combination shorthand values for properties, whilst detecting the type of a value passed as a variable depending on how many values are passed as variables.

  • one value @v1
  • two values @v1 and @v2
  • four values @v1, @v2, @v3 and @v4

And for each variable detected, check if is is a number greater than 0, and if so output a rem and px value for each property value, so the complied css might be something like:

.demobox{
  border-width: 10px 20px 0 0;
  border-width: 1rem 2rem 0 0;
  padding: 0 50px;
  padding: 0 2rem;
  margin: 20px auto;
  margin: 2rem auto;
  font-size: 16px;
  font-size: 1.6rem;
}

This is fairly easy to solve where no more than two values are ever declared, but If I need four values, the number of nested mixins and guards becomes pretty complicated.

The reason for opting for rems is the ease of implementation compared to ems, and prevents having the headache of remembering to have to reset the font-size to every parent item. Additionally the older device support issues are comparable, for both rems and ems, so I figure a px fallback works best.

Here's' the code pen of where I am so far, but I'll try and explain my workings so far as best as I can.

Is there a way to simplify this process? I'm new to less. I imagine there is a much simpler way to detect

  • the number of declared values
  • the type of each value
  • if the number value requires a rem and px value fallback

Thanks

I have a parametric mixin which outputs a rem value and px fallback value along with the declared property

@root-font-size: 10;

.rem(@property, @v1) when (@v1 = @v1){
    @px1: @v1 * @root-font-size;
    @{property}:  ~'@{px1}px';
    @{property}: ~'@{v1}rem';
}

This is great for quickly generating both the rem and pixel fallback for a set css property

/* call the rem mixin */
.demobox {
    .rem(margin, 2);
}
/* output */
.demobox {
    margin: 20px;
    margin: 2rem;
}

One really useful case for this is generating font-size and line-height values with fallbacks

/* call the font-classes mixin */
.font-classes(@fontsize, @lineheight) {
    .rem(font-size, @fontsize);
    .rem(line-height, @lineheight);
}
/* output */
.demobox {
    font-size: 16px;
    font-size: 1.6rem;
    line-height: 24px;
    line-height: 2.4rem;
}

So far so good, i can output a rem and pixel fallback for a single value and nest the .rem mixin with in another mixin.

At the moment, if I want to pass more than one value I have to call the .rem mixin for each required value:

/* call the rem mixin */
.demobox {
    .rem(margin-left, 2);
    .rem(margin-right, 2);
}
/* output */
.demobox {
    margin-left: 20px;
    margin-left: 2rem;
    margin-right: 20px;
    margin-right: 2rem;
}

Ideally I'd like to be able to do is pass either one two or four values with a property to the.rem mixin.

.demobox {
    .rem(margin, 2, 1);
}

As I mentioned earier this is not so difficult for two values, and to check what the value type is. This requires a version of the .rem mixin with a gaurd applied to check that @v2 is declared.

This the triggers a nested .rem-two mixin.

// when there are two values
.rem(@property, @v1, @v2) when (@v1 = @v1) and (@v2 = @v2){
    .rem-two(@property, @v1, @v2);
}

There are three versions of the .rem-two, where the guards are different for each.

Detect if both @v1 and @v2 are numbers greater than 0

.rem-two(@property, @v1, @v2) when (@v1 = @v1) and not (@v1 = 0) and (isnumber(@v1)) and (@v2 = @v2) and not (@v2 = 0) and (isnumber(@v2)) {
    @px1: @v1 * @root-font-size;
    @px2: @v2 * @root-font-size;
    @{property}:  ~'@{px1}px @{px2}px';
    @{property}: ~'@{v1}rem @{v2}rem';
}

Detect if both @v1 and @v2 are numbers greater than 0

.rem-two(@property, @v1, @v2) when (@v1 = @v1) and not (@v1 = 0) and (isnumber(@v1)) and (@v2 = 0), not (isnumber(@v2)){
    @px1: @v1 * @root-font-size;
    @{property}:  ~'@{px1}px @{v2}';
    @{property}: ~'@{v2}rem @{v2}';
}

/* call the rem mixin */
.demobox {
    .rem(margin, 2, 1);
}
/* outputs */
.demobox {
    margin: 10px 20px;
    margin: 1rem 2rem;
}

Detect if both @v1 is a number greater than 0 and @v2 is either a value of 0 or not a number

the '@px2' pixel fallback is not required, so is removed.

.rem-two(@property, @v1, @v2) when (@v1 = @v1) and not (@v1 = 0) and (isnumber(@v1)) and (@v2 = 0), not (isnumber(@v2)){
    @px1: @v1 * @root-font-size;
    @{property}:  ~'@{px1}px @{v2}';
    @{property}: ~'@{v2}rem @{v2}';
}
/* call the rem mixin */
.demobox {
    .rem(margin, 2, auto);
}
/* outputs */
.demobox {
    margin: 10px auto;
    margin: 1rem auto;
}

Detect if both @v1 is either a value of 0 or not a number and @v2 is a number greater than 0

the '@px1' pixel fallback is not required, so is removed.

.rem-two(@property, @v1, @v2) when (@v1 = 0), not (isnumber(@v1)) and (@v2 = @v2) and not (@v2 = 0) and (isnumber(@v2)){
    @px2: @v2 * @root-font-size;
    @{property}:  ~'@{v1} @{px2}px';
    @{property}: ~'@{v1} @{v2}rem';
}
/* call the rem mixin */
.demobox {
    .rem(margin, 0, 20);
}
/* outputs */
.demobox {
    margin: 0 20px;
    margin: 0 2rem;
}

As there are only 3 possibilities for this two value version of the mixin It is easy to solve this problem, but with 3 or four values the number of nested mixins and guards expands to cover all possibilities.

As additional examples. I have repurposed the .rem mixin to output px and rem, border-radius values with vendor prefixing, to ensure that it only processes numeric values, I have guards to check if the value being passed is a number that is greater that 0

.prefix(@property, @v1) when (isnumber(@v1)) and (@v1 > 0) {
    @px1: @v1 * @root-font-size;
    -webkit-@{property}: ~'@{px1}px';
    -moz-@{property}: ~'@{px1}px';
    @{property}: ~'@{px1}px';
    -webkit-@{property}: ~'@{v1}rem';
    -moz-@{property}: ~'@{v1}rem';
    @{property}: ~'@{v1}rem';
}
/* call prefix mixin */
.demobox{
    .prefix(border-radius,5);
}
/* output */
.demobox{
    -webkit-border-radius: 50px;
    -moz-border-radius: 50px;
    border-radius: 50px;
    -webkit-border-radius: 5rem;
    -moz-border-radius: 5rem;
}

I can swap the guards around to check that the value is either 0 (so i can reset a property) or if it is anything else than a number, such as outputting prefixed box sizing.

The reason I ask a guard of not (isnumber(@v1)), rather than (isstring(@v1)), is so I don't have to add single quotes the the value `'border-box'.

.prefix(@property, @v1) when (@v1 = 0), not (isnumber(@v1)) {
    -webkit-@{property}: ~'@{v1}';
    -moz-@{property}: ~'@{v1}';
    @{property}: ~'@{v1}';
}
/* call prefix mixin */
.demobox {
    .prefix(box-sizing, border-box);
}
/* output */
.demobox {
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    box-sizing: border-box;
}

回答1:


You can simplify things if you treat passed arguments as an array (a simplified impl., there're too many requirements in your snippet to cover in an example), Less 1.7.x or higher:

// usage

@root-font-size: 10;

div {
    .rem(border-radius, 1, margin, 2 auto, padding, 4 5 6 inherit);
}

// impl:

.rem-value_(@p, @v, @u) when (isnumber(@v)) {
    @{p}+_: (@v * @u);
}

.rem-value_(@p, @v, @u) when (default()) {
    @{p}+_: @v;
}

.rem(@args...) {
    .i; .i(@i: length(@args)) when (@i > 0) {
        .i((@i - 2));
        @property: extract(@args, (@i - 1));
        @values:   extract(@args,  @i);
        .j(@property, @values, (1px * @root-font-size));
        .j(~'@{property} ', @values, 1rem); // have to use ~'@{property} ' hack to isolate rem and px properties;
    }
    .j(@p, @v, @u, @j: length(@v)) when (@j > 0) {
        .j(@p, @v, @u, (@j - 1));
        .rem-value_(@p, extract(@v, @j), @u);
    }
} 

The loop is quite scary but the value output mixin is pretty transparent and fully customizable (you can add more conditions if you need to).



来源:https://stackoverflow.com/questions/25083818/less-parametric-mixin-with-shorthand-numeric-and-or-string-values-with-rem-an

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!