How to Convert Sass Function to Less?

2019-02-26 06:48发布

问题:

Does anyone know how to recreate the below Sass function in Less? I want to be able to easily convert units within any CSS property (e.g.: font-size, margin, padding, etc).

Sass:

@function get-vw($target) {
    $vw-context: (1440 * 0.01) * 1px;
    @return ($target / $vw-context) * 1vw;
}

CSS:

selector {
    font-size: get-vw(20px)vw;
}

The Sass is from here: http://emilolsson.com/tools/vw-unit-calc-an-online-responsive-css-font-size-calculator/

回答1:

Updated Answer:

As seven-phases-max has mentioned in comments below, he has created an awesome custom Less plugin which allows us to write functions in Less (yes, with return value) and use them within the rules.

For us to make use of this plugin, we must first install the plugin by executing the below command:

npm install -g less-plugin-functions

The next step is for us to write a custom function. The syntax for doing this is first to wrap the function within a mixin/selector block named .function {} and then just copy paste the mixin code that I had given in my original answer (see below). The only change is that @output variable must be replaced with return because the function returns the value that is assigned to this property. So, the code will look like the following:

.function{
  .get-vw(@target) {
    @vw-context: (1440 * 0.01) * 1px;
    return: (@target / @vw-context) * 1vw; /* do calc, set output to "return" property */
  }
}

Once the function is created, usage is very straight-forward and is just like what we do in Sass:

.selector {
  font-size: get-vw(20px);
  margin-top: get-vw(16px);
}

Now, the next step is to compile the Less file to produce the output CSS. Command line compilation is done using the same lessc command but we have to include/invoke the custom plugin while doing it. So the compilation statement would look like the following:

lessc --functions test.less test.css

All these information are already available in the GitHub Page but I've documented them again in the answer for completeness and safety sake.

Note: If no return property is specified within the custom function then the following error would be thrown during compilation:

RuntimeError: error evaluating function get-vw:

[plugin-functions] can't deduce return value, either no mixin matches or return statement is missing in [file name]


Original Answer:

First thing first, there is no way to write a true function in Less as return statements are not possible.

One thing we can do is write a mixin and hack our way around to get it behaving a little close to how a function would. Below is an example:

.get-vw(@target) { /* take an input */
  @vw-context: (1440 * 0.01) * 1px;
  @output: (@target / @vw-context) * 1vw; /* do calc, set an output var */
}

.parent {
  .get-vw(20px); /* expose output var + value to current scope by calling mixin */
  font-size: @output; /* use the value of output var wherever needed */
  .child { /* scoping is not a problem as you can see with the child */
    .get-vw(16px); 
    padding: @output;
    }
}

But the problem with the above snippet is that if two properties need to use the output of this function and each of them have a different input value then there is trouble because of lazy loading of variables in Less. For example, consider the below snippet (.get-vw mixin remains same as earlier snippet.)

.parent {
  .get-vw(20px);
  font-size: @output;
  .get-vw(16px);
  margin-top: @output;
}

You might expect the output to be the following:

.parent {
  font-size: 1.38888889vw;
  margin-top: 1.11111111vw;
}

but the actual output would be this: (as you can see, same value is applied to both)

.parent {
  font-size: 1.38888889vw;
  margin-top: 1.38888889vw;
}

The solution would be to get even more hacky and put each such property in an unnamed namespace (&) and thereby give each one their own scope:

.parent {
  & {
    .get-vw(20px);
    font-size: @output;
  }
  & {
    .get-vw(16px);
    margin-top: @output;
  }
}

As you can see it all gets very very messy. The only way to write a true function in Less would be to write a custom plugin like the one that Bass Jobsen has described in his answer here. It is complex but that's the only true way.