16

Is there any way to pass one mixin or style's declaration to another mixin as an input parameter?

Let's take a look at an example with animation keyframes. Following is how we define keyframes in pure CSS:

@-moz-keyframes some-name
{
    from { color: red; }
    to { color: blue; }
}

@-webkit-keyframes some-name
{
    from { color: red; }
    to { color: blue; }
}

@keyframes some-name
{
    from { color: red; }
    to { color: blue; }
}

Idea is to simplify these declarations using mixins, so we can have something like following:

.keyframes(name, from, to)
{
    // here we need somehow to reproduce structure
    // that we have in an example above
}

// define one animation
.my-from() { color: red; }
.my-to() { color: blue; }
// the following won't work because you cannot pass mixin as a parameter
// in way I have here, so I am looking for a way to solve this problem
.keyframes('some-name', .my-from, .my-to);

// define another animation
.another-from() { font-size: 1em; }
.another-to() { font-size: 2em; }
.keyframes('another-name', .another-from, .another-to);

The system will have different modules that could be dynamically attached to application as well as removed. So, don't suggest me to use @import because it's not the case. Output CSS is dynamically compiled on-fly using information about modules and their own LESS styles as well as base LESS dependencies like mixins library and etc.

Note: it will work for me if you know a way to pass class definition instead of mixin. In an example above it would be .my-from instead of .my-from() and etc.

alex
  • 479,566
  • 201
  • 878
  • 984
Alex M
  • 1,304
  • 1
  • 14
  • 23
  • Can you be more clear as to why you feel you cannot use the mixin directly, and also what you expect the passing of this mixin to give you? Are you trying to use the mixin to define, say, an `@color` variable, and an `@font-size` variable for use within the second mixin? Please be more specific, as Petah's answer would be the normal way to use them. – ScottS Jul 19 '12 at 02:25
  • ScottS, please check my answer to Petah's post. – Alex M Jul 19 '12 at 14:10

4 Answers4

24

UPDATED for LESS 1.7.0+ (WAY Simpler)

We can do this far more directly now with the 1.7.0 update and the ability to create rulesets, and to use variables in setting @keyframes.

Now we really can pass a mixin through a parameter by a ruleset, or we can pass in the property stings themselves. So consider this:

LESS (using 1.7)

.keyframes(@name, @from, @to) {
    @frames: {
        from { @from(); }
        to { @to(); }
    };
    @pre: -moz-keyframes;
    @-moz-keyframes @name
    {
       @frames();
    }

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

    @keyframes @name
    {
       @frames();
    }
}

.keyframes(testName, {color: red; .myMix(0);}, {color: blue; .myMix(1);});

.myMix(@value) {opacity: @value;}

Note that I am passing both a property setting and a mixin call, and my output is:

CSS Output

@-moz-keyframes testName {
  from {
    color: red;
    opacity: 0;
  }
  to {
    color: blue;
    opacity: 1;
  }
}
@-webkit-keyframes testName {
  from {
    color: red;
    opacity: 0;
  }
  to {
    color: blue;
    opacity: 1;
  }
}
@keyframes testName {
  from {
    color: red;
    opacity: 0;
  }
  to {
    color: blue;
    opacity: 1;
  }
}

Note how the rulesets are passed, in brackets {...}, and then called, via @from() and @to() (looking a lot like a mixin call). I'm using these passed rule sets to set another ruleset of @frames which is then itself called to fill the keyframes definitions.

More Generically

Here I pass a private mixin to another mixin and then call it from that other mixin:

LESS

.someMixin(@class; @expectedMixin) {
    .@{class} {
      @expectedMixin();
      .myPrivateMix(0.6);
      test: 1;
    }
}

.someMixin(newClass; {.myClass;});

.myClass {
  .myPrivateMix(@value) {opacity: @value;}
}

CSS Output

.newClass {
  opacity: 0.6;
  test: 1;
}

Kept the below for legacy info.

Updated (added LESS 1.4.0+ support)

Wow, this took some doing, but I think I have something you can work with. However, it does take some special defining of your mixins in your modules, specifically, using pattern matching. So...

First, Define Your Module Mixins

Note how the module mixins intended to be used in a specific future mixin are defined with the same mixin name, but with a different pattern name. This was key to making this work.

// define one animation in a module
.from(my-from){ color: red; }
.to(my-to) { color: blue; }

// define one animation in another module
.from(another-from){ font-size: 1em; }
.to(another-to) { font-size: 2em; }

If you also want individual mixin names in the modules, you should be able to do this:

// define one animation in a module
.my-from(){ color: red; }
.my-to() { color: blue; }

.from(my-from){ .my-from() }
.to(my-to) { .my-to() }   

// define one animation in another module
.another-from(){ font-size: 1em; }
.another-to() { font-size: 2em; }

.from(another-from){ .another-from() }
.to(another-to) { .another-to() }

This should allow one to call either the straight mixin .my-from() or, to make it variably accessible within later mixins that access the singular .from() mixin group through the pattern matching.

Next, Define Your Mixin

For your @keyframes example, that was extremely difficult. In fact, a stack overflow answer was vital to helping me solve an issue with applying the @name, which was not applying under normal LESS rules because of it following the @keyframes definition. The solution to apply the @name looks nasty, but it works. It does have the, perhaps, unfortunate necessity of also defining a selector string to play the animation by (because it uses that string to help build the last } of the keyframes). This naming limitation would only be true of css strings that begin with @ like @keyframes and probably @media.

Further, because we have a standard mixin name used in our module files, we can access that consistently within our new mixin, while at the same time passing a variable in to select the proper variation of that mixin through a pattern match. So we get:

LESS 1.3.3 or under

// define mixin in mixin file

.keyframes(@selector, @name, @from, @to) {
    @newline: `"\n"`; // Newline
    .setVendor(@pre, @post, @vendor) {
        (~"@{pre}@@{vendor}keyframes @{name} {@{newline}from") {
            .from(@from); 
        }    
        to  { 
            .to(@to);
        }
       .Local(){}
       .Local() when (@post=1) {
           (~"}@{newline}@{selector}") {
              -moz-animation: @name;
              -webkit-animation: @name;
              -o-animation: @name;
              -ms-animation: @name;
              animation: @name;
           } 
       }    
       .Local;
    } 
    .setVendor(""            , 0,    "-moz-");
    .setVendor(~"}@{newline}", 0, "-webkit-");
    .setVendor(~"}@{newline}", 0,      "-o-");
    .setVendor(~"}@{newline}", 0,     "-ms-");
    .setVendor(~"}@{newline}", 1,         "");
}

LESS 1.4.0+

.keyframes(@selector, @name, @from, @to) {
    @newline: `"\n"`; // Newline
    .setVendor(@pre, @post, @vendor) {
        @frames: ~"@{pre}@@{vendor}keyframes @{name} {@{newline}from";
        @{frames} {
            .from(@from); 
        }    
        to  { 
            .to(@to);
        }
       .Local(){}
       .Local() when (@post=1) {
           @animationSector: ~"}@{newline}@{selector}";
           @{animationSector} {
              -moz-animation: @name;
              -webkit-animation: @name;
              -o-animation: @name;
              -ms-animation: @name;
              animation: @name;
           } 
       }    
       .Local;
    } 
    .setVendor(""            , 0,    "-moz-");
    .setVendor(~"}@{newline}", 0, "-webkit-");
    .setVendor(~"}@{newline}", 0,      "-o-");
    .setVendor(~"}@{newline}", 0,     "-ms-");
    .setVendor(~"}@{newline}", 1,         "");
}

Now Call Your Mixin

You can give it your own name, and just pass the straight pattern (all are no dot [.] and no quotes) for the pattern matches on the module mixins, but don't forget that you also need a selector string (which is quoted) to get the mixin to work right:

.keyframes('.changeColor', some-name, my-from, my-to);
.keyframes('.changeFontSize', another-name, another-from, another-to);

Which Gives You the Desired Output

@-moz-keyframes some-name {
from {
  color: red;
}
to {
  color: blue;
}
}
@-webkit-keyframes some-name {
from {
  color: red;
}
to {
  color: blue;
}
}
@-o-keyframes some-name {
from {
  color: red;
}
to {
  color: blue;
}
}
@-ms-keyframes some-name {
from {
  color: red;
}
to {
  color: blue;
}
}
@keyframes some-name {
from {
  color: red;
}
to {
  color: blue;
}
}
.changeColor {
  -moz-animation: some-name;
  -webkit-animation: some-name;
  -o-animation: some-name;
  -ms-animation: some-name;
  animation: some-name;
}
@-moz-keyframes another-name {
from {
  font-size: 1em;
}
to {
  font-size: 2em;
}
}
@-webkit-keyframes another-name {
from {
  font-size: 1em;
}
to {
  font-size: 2em;
}
}
@-o-keyframes another-name {
from {
  font-size: 1em;
}
to {
  font-size: 2em;
}
}
@-ms-keyframes another-name {
from {
  font-size: 1em;
}
to {
  font-size: 2em;
}
}
@keyframes another-name {
from {
  font-size: 1em;
}
to {
  font-size: 2em;
}
}
.changeFontSize {
  -moz-animation: another-name
  -webkit-animation: another-name;
  -o-animation: another-name;
  -ms-animation: another-name;
  animation: another-name;
}
Community
  • 1
  • 1
ScottS
  • 71,703
  • 13
  • 126
  • 146
  • And what if the mixin passed as parameter should be invoked with parameters? Using `@to("SomeParam");` does not seem to work – Sebastien Lorber Nov 02 '15 at 16:51
  • @SebastienLorber: It is important to remember that `@to()` is a [ruleset call](http://lesscss.org/features/#detached-rulesets-feature), not a mixin, so it does not take parameters. To pass parameters into the ruleset, look at the example where a mixin call itself is passed as part of the ruleset (e.g. `.myMix(0)`), which mixin was defined as `.myMix(@value) {opacity: @value;}`. I hope that helps you get what you are seeking. – ScottS Nov 02 '15 at 18:25
1

Simplification

I just simplified a little ScottS' way, separateing @keframes from -animation:

.keyframes(@name, @from, @to) {
    @newline: `"\n"`;
    .Local(@x){};
    .Local(@x) when (@x="") {(~"}@{newline}/*"){a:a}/**/};

    .setVendor(@pre, @vendor) {
        (~"@{pre}@@{vendor}keyframes @{name} {@{newline}from") {
            .from(@from);
        }
        to {
            .to(@to);
        }
        .Local(@vendor);
    }
    .setVendor(""            , "-webkit-");
    .setVendor(~"}@{newline}",    "-moz-");
    .setVendor(~"}@{newline}",      "-o-");
    .setVendor(~"}@{newline}",         "");
}

.animation(...) {
  -webkit-animation: @arguments;
     -moz-animation: @arguments;
       -o-animation: @arguments;
          animation: @arguments;
}

use:

.from(a1-from){ width: 10px; }
.to(a1-to) { width: 20px; }
.keyframes(a1-animation, a1-from, a1-to);


.selector {
    // some other css
    .animation(a1-animation 1s infinite linear);
}

output:

@-webkit-keyframes a1-animation {
from {
  width: 10px;
}
to {
  width: 20px;
}
}
@-moz-keyframes a1-animation {
from {
  width: 10px;
}
to {
  width: 20px;
}
}
@-o-keyframes a1-animation {
from {
  width: 10px;
}
to {
  width: 20px;
}
}
@keyframes a1-animation {
from {
  width: 10px;
}
to {
  width: 20px;
}
}
/* {
  a: a;
}
/**/


.selector {
  // some other css
  -webkit-animation: a1-animation 1s infinite linear;
  -moz-animation: a1-animation 1s infinite linear;
  -o-animation: a1-animation 1s infinite linear;
  animation: a1-animation 1s infinite linear;
}

little problem:

So animation is now separated from @keyframes, but we got to pay the price. There is a nasty:

/* {
  a: a;
}
/**/

but it shouldn't be a problem -> propably all of us push CSS files through any kinds of minifiers which cut comments out.

ulfryk
  • 683
  • 1
  • 6
  • 15
1

You can also use my solution to generate CSS keyframes: https://github.com/thybzi/keyframes

Features:

  • Cross-browser keyframes generation (Firefox 5+, Chrome 3+, Safari 4+, Opera 12+, IE 10+)
  • Up to 16 timepoints in each keyframes rule (and the number can be easily augmented, if needed)
  • Mixins, variables and functions can be used for styling timepoints
  • Keyframes are created separately from animation rules, so:
    • multiple animation rules can use the same keyframe with different values for timing, repeating, etc,
    • multiple animations can be used within same animation rule
    • animations can be applied (not created!) inside any parent selector
  • Lightweight and (almost) neat LESS code

Basic usage:

// Preparing styles for animation points
.keyframes-item(fadeIn, 0%) {
    opacity: 0;
}
.keyframes-item(fadeIn, 100%) {
    opacity: 1;
}
// Generating keyframes
.keyframes(fadeIn);

// Applying animation to fade-in block in 1.5 seconds
.myBlock {
    .animation(fadeIn 1.5s);
}
thybzi
  • 1,223
  • 13
  • 15
0

Its not really how you would use mixins.

You should do something along the lines of:

.mixin-one { ... }
.mixin-two { ... }
.target-style {
    .mixin-one;
    .mixin-two;
    font-family: 'Comic Sans MS';
    color: magenta;
}
Petah
  • 45,477
  • 28
  • 157
  • 213
  • Yep, I know that I can do in this way but my problem is more complicated. My system has general mixins rules like library of help-wrappers for css3 stuff and this library is used by different modules, and idea is to pass information from those modules into mixins library to build output css. As you can see there is no way that mixins library would know about mixins declared inside of different external modules. So, I need a way to pass this information into my mixins library. – Alex M Jul 19 '12 at 14:09
  • @JoAsakura--why can you not just `@import` ([see here](http://lesscss.org/#-importing)) your modules into the mixin library and then "all the variables and mixins in them will be made available to the main file." Once imported, then use Petah's method above. – ScottS Jul 19 '12 at 14:41
  • ScottS, it is not my situation. What I have is following: the project contains different modules and each module has its own styles (LESS), all these modules styles use global mixins library. So, there is no `@import` or even possibility that mixin library would know about these external modules. Everything happens dynamically, I build output CSS based on those modules and etc., it is not straight usage of LESS like LESS file + js to compile it. – Alex M Jul 19 '12 at 14:55
  • @JoAsakura (btw--don't forget to use the `@` in front of my name so that it alerts me you have referenced me in the comment). If the mixin library knows nothing of the modules, then how could a mixin from them possibly be in any way used by the mixin library? And I am still unclear as to how exactly you want the module info used by the mixin. – ScottS Jul 19 '12 at 17:58
  • @JoAsakura I agree, we need more information to help you properly. Give use you full use case with examples. – Petah Jul 19 '12 at 21:40
  • @ScottS, Mixins library doesn't need to know anything about where it is being used, mixins are just a collection of "helper-function", right? So, why do you think that mixins need to know about modules? – Alex M Jul 20 '12 at 14:29
  • @ScottS, @Petah, now about my problem: the simplest example is keyframes of animation. You can define all of them locally in a module using `@-webkit-keyframes`, `@-moz-keyframes` and etc. But I want mixins to define this stuff, so I can only define `from` and `to` inside of module and pass them as arguments inside of mixin that defines keyframes structure. It's the simplest example of what I am going to do. – Alex M Jul 20 '12 at 14:30
  • @JoAsakura--the mixin needs the information from the modules, so it needs to know something about those modules. You said you cannot use `@import`, so how are you planning on getting the module info into the mixin file, for the mixin to use it? I only know of `@import`. Your `keyframes` explanation is still hard to follow. Please "edit" your question above, showing what information is coming to the mixin from the module (and how is it getting there), what the mixin intends to do with it, and what css output you are trying to get. You can expand your `keyframes` example, just be more explicit. – ScottS Jul 20 '12 at 14:46
  • @ScottS, I've updated my question, hope it will help you understand the problem. Thx! – Alex M Jul 20 '12 at 17:53
  • @JoAsakura, yay real information. I would look into using SCSS/Compass as it is better at handling this kind of advanced stuff. http://compass-style.org/ – Petah Jul 22 '12 at 10:16
  • @Petah, yep, I know that SCSS provides more flexibility in some cases but my project on node.js and there is better support of LESS. – Alex M Jul 23 '12 at 14:52