3

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/

Harry
  • 87,580
  • 25
  • 202
  • 214
Ryan
  • 314
  • 1
  • 3
  • 12

1 Answers1

3

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.

Community
  • 1
  • 1
Harry
  • 87,580
  • 25
  • 202
  • 214
  • "that's the only true way." - not really, there's a dedicated goody [there](http://lesscss.org/usage/#plugins-list-of-less-plugins). – seven-phases-max Feb 04 '17 at 11:01
  • 1
    "Postprocessor/Feature Plugins" -> "functions". – seven-phases-max Feb 04 '17 at 17:26
  • 1
    That's just bloody awesome @seven-phases-max. Just installed and tried, it is brilliant. Just what Less was missing in my opinion. I will give you a 100 likes just for that plugin! Even after you gave me the link, I had eyes only for that Sass2Less plugin and didn't come down to see this one. I'll edit my answer in a second now. – Harry Feb 04 '17 at 17:38
  • 1
    Functions feature has been continuously planned to be added as a native feature - but there were a lot of fighting about its exact syntax (and still no consensus, hence the plugin). – seven-phases-max Feb 04 '17 at 17:53
  • 1
    @seven-phases-max: Once again have to say thanks for this excellent addition. I really love it. I've edited the answer now, if you find anything wrong (or needing correction) and if you have the time, please feel free to edit my answer :) – Harry Feb 04 '17 at 17:56
  • 1
    @Harry I actually came up with a similar solution that's slightly more versatile, although it seems the plugin that @seven-phases-max mentioned is ultimately the best answer. My solution was to call the mixin with a value of 1: `.get-vw(1);` And then multiply the returned value by whatever size I wanted: `font-size: @output * 20;` or `margin-top: @output * 16;` That simply helps avoid having to call the mixin multiple times and creating separate scopes. It's still not ideal, however. I'll give the plugin a try! – Ryan Feb 04 '17 at 18:03
  • Ah, I get what you mean @Ryan. That's a nice one too and yes, it definitely avoids having to use those `& {}` blocks. The plugin seems to be the best bet as of now. Have a look and if you have any queries, feel free to ping me. – Harry Feb 04 '17 at 18:07