LESS loops used to generate column classes in twit

2020-01-30 02:25发布

问题:

Bootstrap uses some LESS mixins to generate it's column classes (and several other classes);

.make-grid-columns() {
  // Common styles for all sizes of grid columns, widths 1-12
  .col(@index) when (@index = 1) { // initial
    @item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}";
    .col((@index + 1), @item);
  }
  .col(@index, @list) when (@index =< @grid-columns) { // general; "=<" isn't a typo
    @item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}";
    .col((@index + 1), ~"@{list}, @{item}");
  }
  .col(@index, @list) when (@index > @grid-columns) { // terminal
    @{list} {
      position: relative;
      // Prevent columns from collapsing when empty
      min-height: 1px;
      // Inner gutter via padding
      padding-left:  (@grid-gutter-width / 2);
      padding-right: (@grid-gutter-width / 2);
    }
  }
  .col(1); // kickstart it
}

I can see that LESS mixin guards are being used to create loops, and I can understand the code examples that are given in the LESS documentation;

.loop(@counter) when (@counter > 0) {
  .loop((@counter - 1));    // next iteration
  width: (10px * @counter); // code for each iteration
}

div {
  .loop(5); // launch the loop
}

But I can't seem to grok exactly how the more complex nested guard expressions that bootstrap uses are working. Could somebody comment the above bootstrap code in a bit more detail to give me an indication of what is going on?

回答1:

The purpose of the .make-grid-columns() mixin is to generate a long list of selectors which all share the same properties. This list can not be hard code in the code because of the number of columns (@grid-columns) may vary.

You already illustrated the basics of a loop in Less in the question yourself.

To understand the mixins you will have to understand Less allows you the use the same mixin name many times. Every 'matching' mixins will be compiled into the CSS code. Mixin guards when () enable you to set a condition for the match. When the guard not match the mixin is not compiled. Beside mixins guards you can also use pattern matching, than you can match on value as follows:

.mixin1(a,@width){}
.mixin1(b,@width){}

The .mixin(a,20px); call match only the first mixin. Partern matching based on arity (the number of arguments) will also works. Notice that .col(@index) when (@index = 1) does not need a guard (see also). the .col(@index) call has only one argument, so the .col(1); only matches that mixin based on arity matching. The .col(@index) mixin calls the .col(@index, @list) mixin(s). The .col(@index) when (@index = 1) mixin will be only called for the first iteration. The reason for having two mixins in stead of one is that Less don't support if / else. The list of selectors can not start or end with a comma and so the first or last item in the list of selectors should differ from the others.

Alternatively you can use a mixin with an additional argument:

.mixin(@iterator; @item:~""; @seperator:~"") when (@iterator < 5){
@list: ~"@{item} @{seperator} @{iterator}";
.mixin((@iterator + 1); @list; ",");
}
.mixin(@iterator; @list; @seperator) when (@iterator = 5){
.selector{
@{list}: value;
}
}
.mixin(1);

The @seperator will be empty (~"") for the first call and a comma (",") for all other calls. Notice that a mixin with default parameters also match calls which not having set values for the defaults: So .call(1); matches the .call(@a; @b:4; @c:5){} mixin.

As already mentioned in the comments ~"@{list}, @{item}" generates the selector list via string concatenation.

The last iteration of .col(@index, @list) when (@index =< @grid-columns) calls col((@grid-columns + 1)....) mixin when @index=@grid-columns and so matches the last .col(@index, @list) when (@index > @grid-columns) mixin in the structure.

@{list} { } use selector interpolation to set the list of selectors and his properties.

Of course you should also read the excellent blog post of @seven-phases-max about this structure to generate a list of selectors.

Finally you should know that Bootstrap needs such a long list of selectors because of it avoid (partial) attribute selectors. In stead of the selector list you can also use the following CSS / Less code:

[class^="col-"], [class*=" col-"]
{
      position: relative;
      // Prevent columns from collapsing when empty
      min-height: 1px;
      // Inner gutter via padding
      padding-left:  (@grid-gutter-width / 2);
      padding-right: (@grid-gutter-width / 2);
}

The reason to avoid attribute selectors is that some browsers compute them slow. As can be seen at http://benfrain.com/css-performance-revisited-selectors-bloat-expensive-styles/, you can discuse this argument. Personally i think that unused code is a more important performance issue than the use of attribute selectors in most Bootstrap projects.