Sprites Less CSS Variable Increment Issue

Sprites LESS CSS Variable increment issue

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.

Increment a variable in LESS css

Not Strictly Possible

See the documentation on LESS variables. Essentially, LESS variables are constants in the scope of their creation. They are lazy loaded, and cannot be "changed" in that way. The very last definition will be the one used for all in that scope. In your case an error will occur, because variables cannot reference themselves.

Consider this example:

@counter: 1;
.someSelector("nameOfClass", @counter);
@counter: 2;
.someSelector("nameOfClass1", @counter);

.someSelector(@name; @count) {
@className: ~"@{name}";
.@{className} {
test: @count;
}
}

The output will be 2 for both:

.nameOfClass {
test: 2;
}
.nameOfClass1 {
test: 2;
}

This is because LESS defines the @counter with the last definition of the variable in that scope. It does not pay attention to the order of the calls using @counter, but rather acts much like CSS and takes the "cascade" of the variable into consideration.

For further discussion of this in LESS, you might track discussion that occurs on this LESS feature request.

Solution is in Recursive Call Setter for the Local Variable

Seven-phases-max linked to what he believes to be a bug in LESS, but I don't think it is. Rather, it appears to me to be a creative use of recursive resetting of the counter to get the effect desired. This allows for you to achieve what you desire like so (using my example code):

// counter

.init() {
.inc-impl(1); // set initial value
} .init();

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

.someSelector(@name) {
.redefine(); // this sets the value of counter for this call only
.inc-impl((@counter + 1)); // this sets the value of counter for the next call
@className: ~"@{name}";
.@{className} {
test: @counter;
}
}

.someSelector("nameOfClass");
.someSelector("nameOfClass1");

Here is the CSS output:

.nameOfClass {
test: 1;
}
.nameOfClass1 {
test: 2;
}

NOTE: I believe you are not strictly changing a global value here, but rather setting a new local value with each call to .someSelector. Whether this is based on buggy behavior or not is questionable, but if so, this solution may disappear in the future.
For further comments of the limitations of this method, see the discussion here.

Auto-generate LESS styles for sprite icons

Pure LESS (assuming you're using Web Essentials 2013 which uses LESS 1.5.x):

@icons: upvote, downvote, comment, new, notify, search, popup, eye, cross;

.iconize();
.iconize(@i: length(@icons)) when (@i > 0) {
.iconize((@i - 1));

@value: extract(@icons, @i); // LESS arrays are 1-based
.@{value} {background-position: (-20px * (@i - 1)) 0}
.color.@{value} {background-position: (-20px * (@i - 1)) -20px}
.white.@{value} {background-position: (-20px * (@i - 1)) -40px}
}

I removed & from selector names since it has no effect when you generate these classes in the global scope (but put it back if you actually need .iconize to be nested in another ruleset). It is also possible to calculate array length in earlier LESS versions (that have no length function) w/o any javascript, but I don't list this method here since it's quite scary (and you don't need it anyway).


Your javascript based loop is in fact less or more correct but the problem is all values returned by LESS inline javascript are of so-called "anonymous value" type and not a numbers so that when (@c < @count) condition is always true and the loop becomes infinite. (basically the condition is expanded exactly as when (0 < ~'9') ... when (9 < ~'9') = true etc.)

Converting from width to background-width in Less for sprites within CSS class

Just after posting this I looked into where the sprite function came from and found my answer in the generated sprite.less file:

  .sprite-width(@sprite) {
width: ~`"@{sprite}".split(', ')[4]`;
}

.sprite-height(@sprite) {
height: ~`"@{sprite}".split(', ')[5]`;
}

The sprite.less file is generated using a mustache template. In order to solve my problem in Spritesmith you can specify a mustache template. It was also incorrect that I was looking at background-width and the property I needed was background-size. My attempts at inlining also failed, and I should have been looking at using a div rather than sizing the sprite component.

How to write it short in less?

If the usage is <i class="icon-gps" ></i>

i.icon
{
&-gps {
.sprite(@icon-gps);
display:inline-block;
}
&-home {
.sprite(@icon-home);
display:inline-block;
}
&-map {
.sprite(@icon-map);
display:inline-block;
}
}

If the usage is <i class="icon icon-gps"></i> (thanks to @Curt's comment)

i.icon
{
display:inline-block;
&-gps {
.sprite(@icon-gps);
}
&-home {
.sprite(@icon-home);
}
&-map {
.sprite(@icon-map);
}
}

LESS File Extension Variable

Since Modernizr adds classes to the html element for properties supported, and in this case is adding the svg class, it seems like the easiest way to handle the switch in LESS is to do this in your mixin:

.sprite(@x, @y) {
background: url(../img/sprite.png) no-repeat;
background-position: -(@x*@sprite-Grid) -(@y*@sprite-Grid);
.svg & {
background: url(../img/sprite.svg) no-repeat;
}
}

Then when it is used (assuming here @sprite-Grid: 23px;), this LESS:

.testClass {
.sprite(10,10);
}

Produces this CSS:

.testClass {
background: url(../img/sprite.png) no-repeat;
background-position: -230px -230px;
}
.svg .testClass {
background: url(../img/sprite.svg) no-repeat;
}

So it appends the .svg class selector above the whole chain of your selectors, thereby overriding the png with the svg when it is available. This is going to increase your overall size on the CSS file, but does allow the LESS file to be precompiled and prevents you from having to replace the path on the fly for the background images.

Javascript setTimeout scope referencing wrong variables

setAnim looks like it wasn't declared, meaning it's a global variable. This means all your calls to loopAnim are using and overwriting the same timer ID reference.

Animation with Sprites HTML5

I'm not going to answer your question directly because there simply isn't enough working code in your question to easily diagnose the problem.

Based on your description, most likely you aren't clipping the sprite sheet well enough. You need to provide the x/y/w/h coordinates of the piece of the sprite sheet your want to use. It sounds like you aren't doing that.

Here's a very simply working example of animating a sprite on an HTML5 canvas. Note that this will not work in < IE10.

$(function(){    var spritesheet = new Image(),      canvas = $("#game")[0].getContext('2d'),      spriteWidth = 64,      spriteHeight = 64,      indexX = 0,      indexX_max = 7,      indexY = 11,      lastFrameUpdate = 0,      frameRate = 1000 / 16, // 16 frames per second (calculated in milliseconds)      destX = 0,      destY = 0;    spritesheet.src = 'http://i.stack.imgur.com/X2Dt6.png';    function drawNextSprite(){    var currentTime = Date.now(),        timeSinceLastUpdate = currentTime - lastFrameUpdate,        spriteX,        spriteY;        // If it's been less than 1/16 of a second since the last update, then    // don't update the sprite yet    if ( timeSinceLastUpdate < frameRate ){      window.requestAnimationFrame(drawNextSprite);      return;    }        lastFrameUpdate = currentTime;        if ( indexX > indexX_max ) indexX = 0;    if ( destX > 80 ) destX = -spriteWidth;        // find the next sprite image to use    spriteX = spriteWidth * indexX;    spriteY = spriteHeight * indexY;        // clear the area we last drew on (so that we don't leave a trail)    canvas.clearRect( destX, destY, spriteWidth, spriteHeight );    canvas.drawImage(      spritesheet, // the spritesheet      spriteX, spriteY, spriteWidth, spriteHeight, // where is the sprite frame?      destX, destY, spriteWidth, spriteHeight // where should we draw it on the canvas?    );        // increment to the next sprite image    indexX++;    // move our sprite across the screen    destX += 3;        window.requestAnimationFrame(drawNextSprite);  }    drawNextSprite(); // draw the first frame    });
#game{  border: solid black 1px;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script><canvas id="game" height="80" width="80"></canvas>


Related Topics



Leave a reply



Submit