Sprites LESS CSS Variable increment issue

2019-02-19 16:16发布

问题:

I have a problem with sprites background position calculation using a variable:

My code looks something like this:

@counter: 1;

#my-icon-bundle {
.my-icons () {
  #my-icon-bundle .myIconX("classX1", @counter);
  #my-icon-bundle .myIconX("classYY1", @counter);
  ...
}

.myIconX(@name, @index) {
  @nameText: ~".my-icon-@{name}";
  @{nameText} { #my-icon-bundle .myIcon(@index); }

  @counter: @index + 1;
}

.myIcon(@row) {
  @x: some calculations based on @row
  @y: some calculations based on @row
  background-position: -@x -@y;
}
}

The problem is that the @counter increment does not work properly, and all icons appear as the second icon in the sprite image, if we replace:

#my-icon-bundle .myIconX("classX1", @counter);

with the value of the counter it appears correctly...any ideas how to increment the global value properly? Thanks (PS: I'm using less 1.4.2)

回答1:

Strictly Speaking You Cannot

Variables in LESS are essentially constants once defined in a particular scope, and so cannot be changed (including incremented). So your @counter: @index + 1; is not incrementing the global variable at all, but rather creating a new value for a local scope @counter variable inside that particular call of .myIconX(). See the documentation on how variables work in LESS.

Emulated by Recursive Local Variable Setting

This works, based off information deemed a bug here, but which I do not believe is strictly speaking a bug. At any rate, it can be utilized to meet your needs like so (I just implemented an @row: 1 and tweaked some code to show the calculation working):

@row: 1;

.init() {
.inc-impl(1);
} .init();

.inc-impl(@new) {
.redefine() {
@counter: @new;
}
}

#my-icon-bundle {
.my-icons () {
  #my-icon-bundle .myIconX("classX1", @counter);
  #my-icon-bundle .myIconX("classYY1", @counter);
}

.myIconX(@name) {
   .redefine();
  .inc-impl((@counter + 1));
  @nameText: ~".my-icon-@{name}";
  @{nameText} { #my-icon-bundle .myIcon(@row); }
}

.myIcon(@row) {
  @x: @row * @counter;
  @y: @row * @counter;
  background-position: -@x -@y;
}
}

#my-icon-bundle .myIconX("classX1");
#my-icon-bundle .myIconX("classX1");
#my-icon-bundle .myIconX("classYY1");

Output CSS is:

.my-icon-classX1 {
  background-position: -1 -1;
}
.my-icon-classX1 {
  background-position: -2 -2;
}
.my-icon-classYY1 {
  background-position: -3 -3;
}

This demonstrates that with each call of the .myIconX() mixin, it is setting the counter by +1 for the next call.

Warning: Whether this solution is based on buggy behavior or not is questionable, but if it is a bug, this solution may disappear in the future. For further comments on the limitations of this method, see the discussion here.



回答2:

Since a counter based solution seems to still may have some shortcomings depending on possible use-cases (beside the hack-based counter thing itself, see my comment to the corresponding answer) I decided to post a list/loop-based solution I mentioned earlier. I keep the code here as close as possible to the counter-based one so they could be easily compared. (But in general all could be made much clean, structured and generic with further polishing by renaming and reordering all those namespaces/selectors/mixins/variables, removing unnecessary quotes etc.).

Opt. 1

When you need only arbitrary icon(s) of the sprite to have its class in the CSS output:

@row: 1;

// ......

.my-icon-bundle {
    .myIcon(@row, @index) {
        @x: (@row * @index);
        @y: (@row * @index);
        background-position: -@x -@y;
    }

    .myIconX(@name) {
        @icons:
            "classX1",
            "classYY1",
            "classZZZ",
            "anotheRR9",
            "etc.";

        .find(1);
        .find(@i) when (@name = extract(@icons, @i)) {
            @name_: e(@name);
            .my-icon-@{name_} {
                #my-icon-bundle.myIcon(@row, @i);
            }
        }
        .find(@i) when not
            (@name = extract(@icons, @i)) {
                .find((@i + 1));
        }
    }
}

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

#my-icon-bundle.myIconX("anotheRR9");
#my-icon-bundle.myIconX("classX1");

Opt. 2

When you just need to generate corresponding classes for all icons in the sprite:

@row: 1;

// ......

#my-icon-bundle {
    .myIcon(@row, @index) {
        @x: (@row * @index);
        @y: (@row * @index);
        background-position: -@x -@y;
    }

    .icons() {
        @icons:
            "classX1",
            "classYY1",
            "classZZZ",
            "anotheRR9",
            "etc.";

        .make(length(@icons));
        .make(@i) when (@i > 0) {
            .make((@i - 1));
            @name_: e(extract(@icons, @i));
            .my-icon-@{name_} {
                #my-icon-bundle.myIcon(@row, @i);
            }
        }
    }
}

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

#my-icon-bundle.icons();

P.S. All this is for LESS 1.5.x, I'm too lazy to make it compatible with earlier versions - sorry.



标签: css less