@ sign and variables in CSS keyframes using LESS C

2020-02-01 02:19发布

问题:

I'm in need of 8 different CSS3 animations which are way too similar, so I used LESS for it. Below is the code, that works perfectly, with one little glitch - the @name variable.

.animation_top (@name, @pxFrom, @pxTo) {
    @-moz-keyframes @name {
        0% {
            top: @pxFrom;
            opacity: 0;
        }
        100% {
            top: @pxTo;
            opacity: 1;
        }
    }

    @-webkit-keyframes @name {
        0% {
            top: @pxFrom;
            opacity: 0;
        }
        100% {
            top: @pxTo;
            opacity: 1;
        }
    }

    @-ms-keyframes @name {
        0% {
            top: @pxFrom;
            opacity: 0;
        }
        100% {
            top: @pxTo;
            opacity: 1;
        }
    }
}

Because css keyframes are started by @ sign, LESS simply ignores the variable of @name. Is there any way how to escape the keyframe @ sign OR to force LESS to somehow render @name properly?

回答1:

EDIT
Support for the (~"@{varname}") selector will be removed in LESS 1.4.0.
To get the original solution to work, just introduce a temporary variable and make use of selector interpolation (new in LESS 1.3.1).
For the previous example, this would be:

 @tmp: ~"@{varname}"
 @{tmp} { ... }

The explanation below still uses the old selector, because it's conciser. And as shown before, replacing the old method with the new method is trivial.
I did update the code example though, because lots of us blindly copy-paste code.

The expected syntax is (vendorprefixed) (~"@keyframes @{name}") { ... }. However, the output is incorrect (selectors are merged to @keyframes name 0% { ... } @keyframes name 100% {}), because the tree syntax of @keyframes is defined as an exception in less Source code.

The idea behind my crafty mixin is to add curly braces through selectors.

  • The initial selector will be (~"@keyframes @{name}{") { ... }.
    This renders as: @keyframes name {{ ... }
  • Since {{ does not look well, I add a newline. I was not able to escape the newline directly, so I decided to create a variable @newline: `"\n"`;. Less parses anything between backticks as JavaScript, so the resulting value is a newline character. Since { ... } requires a "selector" to be valid, we pick the first step of the animation, 0%.
  • The curly braces do not match. To fix this, we can add a dummy selector in the end, which starts with (~"} dummy") { .. }. This is ugly, because a useless selector is added.
    But wait, we already know that vendor-specific prefixes are going to be added in sequence. So, let the final first selector be (~"@{pre}@@{vendor}keyframes @{name} {@{newline}0%").
    @{pre} has to be "}@{newline}" for every keyframes block after the first one.
  • Now we've dealt with the closing curly brace for every block except for the last one. We do not have to use a useless dummy selector, since we obviously define keyframes in order to use them. animation-name is the property to do so. I'm using a guarded mixin to implement this.

The solution may look somewhat awkward at first, but it's quite concise.

@newline: `"\n"`; // Newline
.animation_top(@selector, @name, @pxFrom, @pxTo) {
    .Keyframe(@pre, @post, @vendor) {
        @keyframe: ~"@{pre}@@{vendor}keyframes @{name} {@{newline}0%";
        @{keyframe} {
            top: @pxFrom;  
            opacity: 0;  
        }    
        100%  { 
            top: @pxTo;
            opacity: 1;
        }    
        .Local(){}
        .Local() when (@post=1) {
            @local: ~"}@{newline}@{selector}";
            @{local} {
                -moz-animation: @name;
                -webkit-animation: @name;
                -o-animation: @name;
                -ms-animation: @name;
                animation: @name;
            } 
        }    
        .Local;
    } 
    .Keyframe(""            , 0,    "-moz-");
    .Keyframe(~"}@{newline}", 0, "-webkit-");
    .Keyframe(~"}@{newline}", 0,      "-o-");
    .Keyframe(~"}@{newline}", 0,     "-ms-");
    .Keyframe(~"}@{newline}", 1,         ""); // <-- Vendorless w3
} 
.animation_top("#test", hey, 10px, 100px);

Is rendered as (notice that the indention inside the keyframes are off by one. This is expected, because Less does not know that we're inside another block, due to the manually added braces).

The following result is confirmed using LESS version 1.3.3 and 1.4.0-b1.

$ lessc --version
lessc 1.3.3 (LESS Compiler) [JavaScript]
$ lessc so
@-moz-keyframes hey {
0% {
  top: 10px;
  opacity: 0;
}
100% {
  top: 100px;
  opacity: 1;
}
}
@-webkit-keyframes hey {
0% {
  top: 10px;
  opacity: 0;
}
100% {
  top: 100px;
  opacity: 1;
}
}
@-o-keyframes hey {
0% {
  top: 10px;
  opacity: 0;
}
100% {
  top: 100px;
  opacity: 1;
}
}
@-ms-keyframes hey {
0% {
  top: 10px;
  opacity: 0;
}
100% {
  top: 100px;
  opacity: 1;
}
}
@keyframes hey {
0% {
  top: 10px;
  opacity: 0;
}
100% {
  top: 100px;
  opacity: 1;
}
}
#test {
  -moz-animation: hey;
  -webkit-animation: hey;
  -o-animation: hey;
  -ms-animation: hey;
  animation: hey;
}

Final notes:

  • The shortest dummy which produces valid CSS is /**/. Example: (~"..") {/**/} -> .. {/**/}.


回答2:

Less version 1.7 now supports a way nicer method:

MIXIN

.keyframes(@name) { 
    @-webkit-keyframes @name {
        .-frames(-webkit-);
    }
    @-moz-keyframes @name {
        .-frames(-moz-);
    }
    @keyframes @name {
        .-frames();
    }
}

INPUT

& {
    .keyframes(testanimation);.-frames(@-...){
        0% {
            left: 0;
            @{-}transform: translate(10px, 20px);
        }

        100% {
            left: 100%;
            @{-}transform: translate(100px, 200px);
        }
    }
}


回答3:

Perhaps this is what you want? If you define @name: "ANIM_NAME"; then I suppose this is the way:

@-moz-keyframes @{name} {
...
...
}