Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
506 views
in Technique[技术] by (71.8m points)

escaping - @ sign and variables in CSS keyframes using LESS CSS

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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: `" "`;. 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: `"
"`; // 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: (~"..") {/**/} -> .. {/**/}.

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...