Tuesday 13 September 2016

How To Write Your Own Custom Sass Functions - Vanseo Design

How To Write Your Own Custom Sass Functions - Vanseo Design


How To Write Your Own Custom Sass Functions

Posted: 13 Sep 2016 05:30 AM PDT

If you've built more than a handful of websites, you've probably noticed you can easily repeat code in a project and across multiple projects. Repeating the same code over and over wastes your time and increases the possibility of introducing errors. In a previous series on Sass, I said mixins were one option for reusing styles and writing DRYer code. Functions are another way to do the same.

Qwerty Keyboard
Qwerty Keyboard by Mysid

For the last couple of months I've been talking about data types, operators, and functions in Sass. I walked you through numbers, strings, colors, colors again, lists, lists again, and maps. The last couple of weeks I talked about control directives. Today marks the last post in this series and I want to close the series talking about the @function directive and how to write your own custom functions.

How to Create and Use Custom Functions

Functions are blocks of code that return a single value of any Sass data type. To create a custom function you need two Sass directives, @function and @return. The first creates the function and the second signals the value the function will return.

1  2  3  
@function function-name($args) {     @return value-to-be-returned;    }

The arguments passed to the function are optional, though you'll use them more often than not. Typically a Sass function will run some calculation(s) using the arguments that have been passed to it and possibly some global variables, which all functions can access.

For historical reasons function names can use either a dash or an underscore interchangeably so function-name and function_name are the same function and you can use either the dash or the underscore to refer to the function regardless of which one was used when naming it.

In the code above I show only a single line of code to return a value, but most functions will do more and then the @return directive will be included as the last line of the function.

Keep in mind that while only a single value is returned that value can be of any data type so you could return the number 9, the string "I am a string." or a data structure like a list or map that includes as many values as you'd like.

To use a function you supply the function name and any arguments in place where you want the returned value to appear.

1  2  3  4  5  6  7  
p {     font-size: function-name($args);    }    p {     font-size: function_name($args);    }

Again these would both be calls to the same function. Presumably this function would calculate a value in px or em and return that value so it would become the font-size of the paragraph.

As I said, functions can access any globally defined variable. In this next example I created two global variables, $total and $col. The first might represent the total number of columns in a grid and the second might represent a grid field that's 3 columns wide.

1  2  3  4  5  6  
$total: 8;    $col: 3    @function column-width() {     @return percentage($col/$total);    }

The function takes no arguments. It divides the size of the field ($col) by the total number of columns ($total) and it uses the built-in percentage function to turn the result into a percentage before it returns the value.

We can call the function by adding the function name where we want the returned value to appear. Since this function doesn't take arguments, none are passed with the function call.

1  2  3  
.col-3 {     width: column-width();    }

The function will do the math and the code will be compiled to:

1  2  3  
.col-3 {     width: 37.5%;    }

The function as written isn't very useful as I had to hard code the total number of columns and the size of the grid field as global variables. A more useful scenario would be to pass these as arguments so let's rewrite the function.

1  2  3  
@function column-width($col, $total) {     @return percentage($col/$total);    }

Because the function now accepts arguments we have to pass them during the function call, though we can reuse the function and call it with different arguments

1  2  3  4  5  6  7  
.col-3 {     width: column-width(3, 8);    }    .col-5 {     width: column-width(5, 8);    }

which would compile to:

1  2  3  4  5  6  7  
.col-3 {     width: 37.5%;    }    .col-5 {     width: 62.5%;    }

Keyword Arguments

You might have noticed that in the previous example I passed the arguments in the order they're listed in the function. That's necessary unless you use keywords.

You can pass a key/value pair using the form key: value.

1  2  3  
.col-3 {     width: column-width($col: 3, $total: 8);    }

If you include the key name, you don't have to list them in the same order they're listed in the function.

1  2  3  
.col-3 {     width: column-width($total: 8, $col: 3);    }

Even though the function lists $col first and $total second, by using the keywords before each value the function knows which is which.

You can also declare defaults on the arguments when creating the function. Here I've modified the function so the default number of total columns is 8.

1  2  3  4  5  6  7  
@function column-width($col, $total:8) {     @return percentage($col/$total);    }    .col-3 {     width: column-width(3);    }

In the code above I only passed a single value to the function. The function then uses the default for the argument that I didn't pass and still compiles to:

1  2  3  
.col-3 {     width: 37.5%;    }

You can always override the default by passing the argument with or without the keyword.

1  2  3  4  5  6  7  
.col-3 {     width: column-width(3, 9);    }    .col-4 {     width: column-width($col:3, $total:12);    }

A More Practical Example

So far none of the examples have been all that practical. I've written them as I have to explain how something works. Let's write some code that's a little more practical.

Here I set the global variable $total as 8. Then I rewrote the function from the previous examples to use the global variable instead of having to pass the total number of columns as a parameter.

Finally I used a for loop to run from 1 to the value of $total. Inside the loop, the counter $i becomes part of the class name and the width of the class generated by passing the value of $i to the function.

1  2  3  4  5  6  7  8  9  
$total: 8;    @function column-width($col) {     @return percentage($col/$total);    }    @for $i from 1 through $total {     .col-#/{$i/} { width: column-width($i) };    }

The Sass compiles to:

1  2  3  4  5  6  7  8  
.col-1 { width: 12.5%; }    .col-2 { width: 25%; }    .col-3 { width: 37.5%; }    .col-4 { width: 50%; }    .col-5 { width: 62.5%; }    .col-6 { width: 75%; }    .col-7 { width: 87.5%; }    .col-8 { width: 100%; }

If instead of an 8-column layout, you decide on to use 12 columns or 9 or 4, all you'd have to do is change the value of $total. The custom function and @for loop will do the rest.

Variable Arguments

Like mixins, functions can take variable arguments by using 3 dots (not an ellipses) after the argument name. The function will create a list from the variable arguments passed to it.

In the following example I created a function that takes an argument named $index and a variable argument named $widths…

1  2  3  
@function column-width($index, $widths...){      @return nth($widths, $index);    }

The function will use the built-in nth function to find the value inside $widths at index $index. When I call the function, I pass it a value for the index and a series of percentage widths.

1  2  3  
.col-3 {      width: column-width(3, 25%, 50%, 75%, 100%);    }

Since 75% is the value at index 3, the code compiles to:

1  2  3  
.col-3 {      width: 75%;    }

Quite honestly this isn't a particularly useful function as it would have been easier to simply set the width instead of calling the function. While variable arguments are useful in mixins to add something like multiple box shadows, I have a hard time thinking how they would be useful in a function. Still it's something you can do and I thought worth showing.

Prefix Your Function Names

One thing I should mention is that it's a good idea to give your function names a prefix to avoid naming conflicts with built-in functions or functions from a library you might be using.

It isn't hard to imagine someone else having created a column-width function that would conflict with the function names I've used in my examples. A better name might be vanseo-column-width, since it's not likely that a 3rd party function would use the same prefix.

Function or Mixin

Because Sass functions and Sass mixins are similar you might wonder why you would use one over the other. While similar they do have one important difference, which suggests when it's best to use each.

The main difference between the two is that mixins output lines of Sass code that will compile directly into CSS styles, while functions return a value that can then become the value for a CSS property or become something that might be passed to another another function or mixin.

To be honest you could probably rewrite every function to be a mixin and vice versa, but there are clear uses cases for each.

Use a mixin when you want to directly output styles and use functions when you want to perform calculations that returns a value. For example the formula to determine the percent width of a responsive container might look familiar

1  
target / context = result

Because this is a calculation you'd likely perform multiple times in a stylesheet it's ripe for a function where you pass the target and context and get the resulting width in return.

Closing Thoughts

Functions are one way to move reusable code into a separate package. They're similar to mixins and they differ mainly in their output. Functions return a value where mixins output code. If you know how to work with one, it shouldn't be too difficult to work with the other.

That brings us to the end of this series on Sass data types, operators, and functions. Hopefully now that we've reached the end of the series, you can see why I spent time talking about each of the different data types. Outside of colors, the other data types become more useful when used in functions or mixins.

Next up I want to return to SVG and a series about filter effects, which allow you to do things like blur an image, add lighting effects, or blend two images together.

Download a free sample from my book Design Fundamentals.

Join me as I share my creative process and journey as a writer.

This posting includes an audio/video/photo media file: Download Now