Less CSS - 减少重复

Less CSS - cut down repetition

我在 Less 中有以下代码:

我很确定这可以通过某种混音进一步抽象出来,但我已经摸索了一段时间了。我希望能够传入一个变量,例如@xs 或@xs-gutter,并让函数填充代码。

有什么想法吗?

.padding
{
    &.bottom {
        padding-bottom: @xs-gutter;
    }
    &.left {
        padding-left: @xs-gutter;
    }
    &.right {
        padding-right: @xs-gutter;
    }
    &.top {
        padding-top: @xs-gutter;
    }

    @media @sm-screen {
        &.bottom {
            padding-bottom: @sm-gutter;
        }
        &.left {
            padding-left: @sm-gutter;
        }
        &.right {
            padding-right: @sm-gutter;
        }
        &.top {
            padding-top: @sm-gutter;
        }
    }
    @media @md-screen {
        &.bottom {
            padding-bottom: @md-gutter;
        }
        &.left {
            padding-left: @md-gutter;
        }
        &.right {
            padding-right: @md-gutter;
        }
        &.top {
            padding-top: @md-gutter;
        }
    }
    @media @lg-screen {
        &.bottom {
            padding-bottom: @lg-gutter;
        }
        &.left {
            padding-left: @lg-gutter;
        }
        &.right {
            padding-right: @lg-gutter;
        }
        &.top {
            padding-top: @lg-gutter;
        }
    }
}

您可以使用循环和数组列表来减少代码中的重复。以下是关于如何实现 reduction.Refer 内联注释的示例片段,用于解释代码的作用。

Note: I have made the actual padding generation mixin as a separate one which takes the sides as an argument because you can re-use that mixin to generate padding for multiple sides (by passing the sides and gutter as arguments) without generating media-queries for them.

@gutters: 4px, 6px, 8px, 10px; // the gutter sizes corresponding to each screen size
@media-sizes: xs, sm, lg, md; // possible screen sizes
@media-conditions: ~"(min-width: 100px)", ~"(min-width: 150px)", ~"(min-width: 200px)", ~"(min-width: 250px)"; // media condition for each screen size

.media-generator(){
    .loop-sizes(length(@media-sizes)); // loop through all screen sizes
    .loop-sizes(@screenIndex) when (@screenIndex > 0) {
        & when (extract(@media-sizes, @screenIndex) = xs){ // since we need xs as default
            .padding-per-side(extract(@gutters, 1); left; right; bottom; top);
        }
        & when not (extract(@media-sizes, @screenIndex) = xs){ // when screen size is not xs
            @condition: extract(@media-conditions, @screenIndex); // extract media condition corresponding to screen type
            @media @condition{
                .padding-per-side(extract(@gutters, @screenIndex); left; right; bottom; top); // call the mixin to generate padding for all sides
            }
        }
        .loop-sizes(@screenIndex - 1);
    }
}
.padding-per-side(@gutter; @sides...){
    .loop-sides(length(@sides));
    .loop-sides(@index) when (@index > 0){
        @side: extract(@sides, @index);
        &.@{side}{
            padding-@{side}: @gutter;
        }
        .loop-sides(@index - 1);
    }
}
.padding{
    .media-generator(); // generate padding for all sides and screens like in question
}

#demo{ // extra :)
    .padding-per-side(10px; left;right); // generates 10px padding for left and right
}

下面是上面的增强版本,它允许我们只为某些方面生成带有媒体查询的填充。下面的代码片段和上面的代码片段之间的区别在于,在这里您可以单独为特定的边生成填充以及它们的媒体查询版本。

@gutters: 4px, 6px, 8px, 10px;
@media-sizes: xs, sm, lg, md;
@media-conditions: ~"(min-width: 100px)", ~"(min-width: 150px)", ~"(min-width: 200px)", ~"(min-width: 250px)";

.media-generator(@sides...){
    & when (length(@sides) = 0){
        .loop-sizes(length(@media-sizes));
        .loop-sizes(@screenIndex) when (@screenIndex > 0) {
            & when (extract(@media-sizes, @screenIndex) = xs){
                .padding-per-side(extract(@gutters, 1); left; right; bottom; top);
            }
            & when not (extract(@media-sizes, @screenIndex) = xs){
                @condition: extract(@media-conditions, @screenIndex);
                @media @condition{
                    .padding-per-side(extract(@gutters, @screenIndex); left; right; bottom; top);
                }
            }
            .loop-sizes(@screenIndex - 1);
        }
    }
    & when not (length(@sides) = 0){
        .loop-sizes(length(@media-sizes));
        .loop-sizes(@screenIndex) when (@screenIndex > 0) {
            & when (extract(@media-sizes, @screenIndex) = xs){
                .padding-per-side(extract(@gutters, 1); @sides);
            }
            & when not (extract(@media-sizes, @screenIndex) = xs){
                @condition: extract(@media-conditions, @screenIndex);
                @media @condition{
                    .padding-per-side(extract(@gutters, @screenIndex); @sides);
                }
            }
            .loop-sizes(@screenIndex - 1);
        }
    }   
}
.padding-per-side(@gutter; @sides...){
    .loop-sides(length(@sides));
    .loop-sides(@index) when (@index > 0){
        @side: extract(@sides, @index);
        &.@{side}{
            padding-@{side}: @gutter;
        }
        .loop-sides(@index - 1);
    }
}
.padding{
    .media-generator(left; right); // specify sides if needed else leave blank
}

我个人细化了3种可能的方案,从最简单的开始,然后优化到第3种。最简单的具有更好的可读性,最难的使用双重嵌套 LOOP.

在这里,我报告了 3 个解决方案之间的 "common" 代码(为了更直观,我决定保留单独的可能大小定义):

@sm-screen:~"(min-width: 320px)";
@md-screen:~"(min-width: 720px)";
@lg-screen:~"(min-width: 1200px)";

@xs-gutter:20px;
@sm-gutter:30px;
@md-gutter:40px;
@lg-gutter:50px;

@property:padding;
//@property:margin;

请注意,@property可以是“margin”或“padding”,只是切换注释而已。

对于这个变量定义,您可以附加以下 3 个建议:


解决方案 1

这是最简单的解决方案。缺点是它会为每个规则生成不同的mediaquery,导致代码冗余:

.side(top, right, bottom, left);

.side(@possible-values...) 
{
  .generate-property-loop(1, @possible-values);
}

.generate-property-loop(@var; @possible-values) when (@var <= length(@possible-values)) 
{
  //Let's extract values in @var position from list @possible-values
  @direction: extract(@possible-values, @var);

  .@{property}.@{direction} 
  {
        @{property}-@{direction}: @xs-gutter;

        @media @sm-screen 
        {
           @{property}-@{direction}: @sm-gutter;
        }

        @media @md-screen 
        {
           @{property}-@{direction}: @md-gutter;
        }

        @media @lg-screen 
        {
           @{property}-@{direction}: @lg-gutter;
        }

  }

  .generate-property-loop((@var + 1), @possible-values);
}

解决方案 2

一个可能的解决方案是将媒体查询移到 LOOP 之外,但它仍然需要明确的媒体查询定义:

.side(top, right, bottom, left);

.side(@possible-values...) 
{
  .generate-property-loop(1, @possible-values, @xs-gutter);

  @media @sm-screen 
  {
    .generate-property-loop(1, @possible-values, @sm-gutter);
  }

  @media @md-screen 
  {
    .generate-property-loop(1, @possible-values, @md-gutter);
  }

  @media @lg-screen 
  {
    .generate-property-loop(1, @possible-values, @lg-gutter);
  }

}

.generate-property-loop(@var, @possible-values, @gutter) when (@var <= length(@possible-values)) 
{
  //Let's extract values in @var position from list @possible-values
  @direction: extract(@possible-values, @var);

  .@{property}
  {
     &.@{direction} 
     {
        @{property}-@{direction}: @gutter;
     } 
  }

  .generate-property-loop((@var + 1), @possible-values, @gutter);
}

解决方案 3

使用双嵌套循环,您可以获得完全的灵活性,只需将 "directions" 和 "scren-size" 作为参数传递,但代价是可读性较小:

.side(top, right, bottom, left);

.side(@possible-values...) 
{
  .generate-property-loop(1, @possible-values, @xs-gutter);

  .mediaquery-loop(sm,md,lg);
}

.mediaquery-loop(@possible-screens...)
{
  .generate-mediaquery-loop(1, @possible-screens);
}

.generate-property-loop(@var, @possible-values, @gutter) when (@var <= length(@possible-values)) 
{
  @direction: extract(@possible-values, @var);

  .@{property}
  {
     &.@{direction} 
     {
        @{property}-@{direction}: @gutter;
     } 
  }

  .generate-property-loop((@var + 1), @possible-values, @gutter);
}

.generate-mediaquery-loop(@var, @possible-sizes) when (@var <= length(@possible-screens)) 
{
  @sizes: extract(@possible-sizes, @var);

  @screen-size: ~"@{sizes}-screen";
  @gutter-size: ~"@{sizes}-gutter";

  @media @@screen-size
  {
    .generate-property-loop(1, @possible-values, @@gutter-size);
  }

  .generate-mediaquery-loop((@var + 1), @possible-screens);
}