In Sass, functions and mixins are very similar. They have the following characteristics in common:
The areas were they divert from each other are:
@content
directive cannot be used with functionsBecause of their similarities and despite their differences, functions and mixins are sometimes used in an incorrect manner. For example, let's take a look at the following mixin that allows to add a border to a block element:
$border-position-all: 'all' !default;
$border-default-size: 1px !default;
$border-default-pattern: solid !default;
$border-default-color: #000 !default;
@mixin add-border($border-position: $border-position-all,
$border-size: $border-default-size, $border-pattern: $border-default-pattern,
$border-color: $border-default-color) {
@if $border-position == 'all' {
border: $border-size $border-pattern $border-color;
} @else {
border-#{$border-position}: $border-size $border-pattern $border-color;
}
}
This mixin can be written as function in the following manner:
$border-default-size: 1px !default;
$border-default-pattern: solid !default;
$border-default-color: #000 !default;
@function add-border-fn($border-size: $border-default-size,
$border-pattern: $border-default-pattern, $border-color: $border-default-color) {
$border: $border-size $border-pattern $border-color;
@return $border;
}
This may go under the rule of "Just because you can doesn't mean that you should". The rule that we follow when it comes to implementing an abstracted logic with a custom function or a mixin is the following:
emCalculator
function that we created earlier in this chapter where it takes a pixel
value and converts it to em
values.Here is an example of a mixin created to handle the variety media query strategies needed for a responsive website:
// ==== media queries ======================================================
// EXAMPLE Media Query for Responsive Design.
// This example overrides the primary ('mobile first') styles
// Modify as content requires.
// ==========================================================================
//Responsive
//-----------------------------
$small-screen-min-width: em(320px) !default;
$small-screen-max-width: em(767px) !default;
$medium-screen-min-width: em(768px) !default;
$medium-screen-max-width: em(1024px) !default;
$large-screen-min-width: em(1025px) !default;
$screen: "only screen" !default;
$small: "only screen and (min-width:#{$small-screen-min-width}) and (max-width:#{$small-screen-max-width})" !default;
$medium: "only screen and (min-width:#{$medium-screen-min-width}) and (max-width:#{$medium-screen-max-width})" !default;
$large: "only screen and (min-width:#{$large-screen-min-width})" !default;
$landscape: " and (orientation: landscape)" !default;
$portrait: " and (orientation: portrait)" !default;
@mixin respond-to($media, $orientation: false) {
@if $media == smartphone {
@if $orientation {
@if $orientation == landscape {
@media #{$small} #{$landscape} { @content}
} @else if $orientation == portrait {
@media #{$small} #{$portrait} { @content}
}
} @else {
@media #{$small} { @content}
}
} @else if $media == tablet {
@if $orientation {
@if $orientation == landscape {
@media #{$medium} #{$landscape} { @content}
} @else if $orientation == portrait {
@media #{$medium} #{$portrait} { @content}
}
} @else {
@media #{$medium} { @content}
}
} @else if $media == desktop {
@media #{$large} {@content}
}
}
// ==== End media queries ======================================================
After reviewing this mixin further we have decided to abstracted some of the elements of this mixins, particularly the area where we are handling the logic to build the @media
label based on the media type that is being passed to the mixin and the function. Here is the mixin, re-written:
// ==|== media queries ======================================================
// EXAMPLE Media Query for Responsive Design.
// This example overrides the primary ('mobile first') styles
// Modify as content requires.
// ==========================================================================
//Responsive
//-----------------------------
$screen: "only screen" !default;
$landscape: " and (orientation: landscape)" !default;
$portrait: " and (orientation: portrait)" !default;
$media-query-sizes: (
small: (
min: em(320px),
max: em(767px)
),
medium: (
min: em(768px),
max: em(1024px)
),
large: (
min: em(1025px)
)
);
@function media-label($media, $orientation: false) {
@if(not map-has-key($media-query-sizes, $media)){
@warn "the $media value needs to be one of the following #{map-keys($media-query-sizes)}";
@return false;
}
$media-sizes: map-get($media-query-sizes, $media);
$media-label: $screen + " and (min-width:#{map-get($media-sizes, 'min')})";
@if(length($media-sizes) > 1) {
$media-label: $media-label + " and (max-width:#{map-get($media-sizes, 'max')})";
}
@if $orientation {
@if $orientation == landscape {
$media-label: $media-label + $landscape;
} @else {
$media-label: $media-label + $portrait;
}
}
@return $media-label;
}
@mixin respond-to($media, $orientation: false) {
$media-query-label: media-label($media, $orientation);
@if $media-query-label {
@media #{media-label($media, $orientation)} {
@content
}
}
}
// ==== End media queries ======================================================
As you can see, we have abstracted the following variables:
$small-screen-min-width: em(320px) !default;
$small-screen-max-width: em(767px) !default;
$medium-screen-min-width: em(768px) !default;
$medium-screen-max-width: em(1024px) !default;
$large-screen-min-width: em(1025px) !default;
$screen: "only screen" !default;
$small: "only screen and (min-width:#{$small-screen-min-width}) and (max-width:#{$small-screen-max-width})" !default;
$medium: "only screen and (min-width:#{$medium-screen-min-width}) and (max-width:#{$medium-screen-max-width})" !default;
$large: "only screen and (min-width:#{$large-screen-min-width})" !default;
$landscape: " and (orientation: landscape)" !default;
$portrait: " and (orientation: portrait)" !default;
And created a map that contains the breakpoints that we are interested in:
$screen: "only screen" !default;
$landscape: " and (orientation: landscape)" !default;
$portrait: " and (orientation: portrait)" !default;
$media-query-sizes: (
small: (
min: em(320px),
max: em(767px)
),
medium: (
min: em(768px),
max: em(1024px)
),
large: (
min: em(1025px)
)
);
We have created a function that will create the media label based on the variables that have been given:
@function media-label($media, $orientation: false) {
@if(not map-has-key($media-query-sizes, $media)){
@warn "the $media value needs to be one of the following #{map-keys($media-query-sizes)}";
@return false;
}
$media-sizes: map-get($media-query-sizes, $media);
$media-label: $screen + " and (min-width:#{map-get($media-sizes, 'min')})";
@if(length($media-sizes) > 1) {
$media-label: $media-label + " and (max-width:#{map-get($media-sizes, 'max')})";
}
@if $orientation {
@if $orientation == landscape {
$media-label: $media-label + $landscape;
} @else {
$media-label: $media-label + $portrait;
}
}
@return $media-label;
}
This will allow us to re0use this logic in any area that is needed. Our current need is in out respond-to
mixin:
@mixin respond-to($media, $orientation: false) {
$media-query-label: media-label($media, $orientation);
@if $media-query-label {
@media #{media-label($media, $orientation)} {
@content
}
}
}
Now we use this mixin wherever we need to add a media query in the following manner:
@include respond-to (small) {
body {
background-color: red;
}
}
@include respond-to (small, landscape) {
body {
background-color: blue;
}
}
@include respond-to (medium) {
body {
background-color: green;
}
}
@include respond-to (medium, landscape) {
body {
background-color: yellow;
}
}
@include respond-to (large) {
body {
background-color: black;
}
}
@include respond-to (gubliguke) {// this one will error out and will not be displayed
body {
background-color: black;
}
}
The above Sass will compile into the following CSS:
@media only screen and (min-width: 20em) and (max-width: 47.9375em) {
body {
background-color: red;
}
}
@media only screen and (min-width: 20em) and (max-width: 47.9375em) and (orientation: landscape) {
body {
background-color: blue;
}
}
@media only screen and (min-width: 48em) and (max-width: 64em) {
body {
background-color: green;
}
}
@media only screen and (min-width: 48em) and (max-width: 64em) and (orientation: landscape) {
body {
background-color: yellow;
}
}
@media only screen and (min-width: 64.0625em) {
body {
background-color: black;
}
}
Functions and mixins are the backbone of Sass development and are extremely useful in promoting DRY Sass. Use them wisely and often.