Trigonometry in Sass

Have you ever found yourself needing trigonometric functions like sines, cosines and tangents when writing your Sass stylesheets? Ok, probably not, but the day may come, and you’ll be glad you read this.

Sass provides mathematical operators like addition and multiplication, and basic language constructs like conditionals and loops, but not much more. I’ve always relied on built-in methods, like Math.sin and Math.cos in JavaScript or Ruby, for these kinds of complex calculations…

Is there a way to approximate a sine or a cosine iteratively? It turns out there are plenty.

Numerical methods#

If I had paid more attention in numerical analysis class, I may have been able to tell you everything about the CORDIC algorithm, the Chebyshev polynomials, or the Remez algorithm. But I didn’t, so the best I can do is to copy and paste the Taylor expansions for the sine and cosine functions:

\begin{aligned} \sin x & = \sum _{n=0}^{\infty}{\frac {(-1)^{n}}{(2n+1)!}}x^{{2n+1}} = x - {\frac {x^{3}}{3!}} + {\frac {x^{5}}{5!}} - \cdots \\ \cos x & = \sum _{n=0}^{\infty}{\frac {(-1)^{n}}{(2n)!}}x^{{2n}} = 1 - {\frac {x^{2}}{2!}} + {\frac {x^{4}}{4!}} - \cdots \end{aligned}

Believe it or not, these equations give approximations that are good enough for our needs. So now we only have to translate them to Sass.

Power#

Numerators $x^{(2n+1)}$ and $x^{(2n)}$ require an exponentiation operator, but we don’t have one in Sass, so we’ll have to implement our own power function:

\begin{aligned} b^{n} & = {\overbrace {b \times \cdots \times b}^{n}} \\ b^{-n} & = {\frac {1}{\underbrace {b \times \cdots \times b}_{n}}} \end{aligned}

Which could be translated into something like this:

@function pow($number,$exp) {
$value: 1; @if$exp > 0 {
@for $i from 1 through$exp {
$value:$value * $number; } } @else if$exp < 0 {
@for $i from 1 through -$exp {
$value:$value / $number; } } @return$value;
}


Factorial#

Denominators $(2n+1)!$ and $(2n)!$ require us to implement a factorial function, but this is even more straightforward:

$n! = { \begin{cases} 1 & \text{if } n = 0 \\ (n-1)! \times n & \text{if } n > 0 \end{cases} }$

Which could look like this:

@function fact($number) {$value: 1;
@if $number > 0 { @for$i from 1 through $number {$value: $value *$i;
}
}
@return $value; }  Sines, cosines and tangents# Now we have all the necessary pieces to create our trigonometric functions, following the formulas of the Taylor expansions. Here we go: @function pi() { @return 3.14159265359; } @function rad($angle) {
$unit: unit($angle);
$unitless:$angle / ($angle * 0 + 1); // If the angle has 'deg' as unit, convert to radians. @if$unit == deg {
$unitless:$unitless / 180 * pi();
}
@return $unitless; } @function sin($angle) {
$sin: 0;$angle: rad($angle); // Iterate a bunch of times. @for$i from 0 through 10 {
$sin:$sin + pow(-1, $i) * pow($angle, (2 * $i + 1)) / fact(2 *$i + 1);
}
@return $sin; } @function cos($angle) {
$cos: 0;$angle: rad($angle); // Iterate a bunch of times. @for$i from 0 through 10 {
$cos:$cos + pow(-1, $i) * pow($angle, 2 * $i) / fact(2 *$i);
}
@return $cos; } @function tan($angle) {
@return sin($angle) / cos($angle);
}


Let’s see if they work as expected:

@debug sin(pi() / 4); // => 0.70711
@debug cos(45deg); // => 0.70711


High fives all around!

Alternatives#

If you are using Compass you already have plenty of math helpers at your disposal, so you don’t need to write your own.

If you are not using Compass, but you don’t mind getting your hands dirty and writing some Ruby code, you could extend the Sass::Script::Functions module with whatever functions you want. To do that, create a file (e.g. sass_math.rb) and paste the following contents:

require 'sass'

module Sass::Script::Functions

module CustomMath

def pi()
Sass::Script::Number.new(Math::PI)
end
Sass::Script::Functions.declare :pi, []

def sin(number)
trig(:sin, number)
end
Sass::Script::Functions.declare :sin, [:number]

def cos(number)
trig(:cos, number)
end
Sass::Script::Functions.declare :cos, [:number]

def tan(number)
trig(:tan, number)
end
Sass::Script::Functions.declare :tan, [:number]

private

def trig(operation, number)
if number.numerator_units == ['deg'] && number.denominator_units == []
Sass::Script::Number.new(Math.send(operation, Math::PI * number.value / 180))
else
Sass::Script::Number.new(Math.send(operation, number.value), number.numerator_units, number.denominator_units)
end
end

end

include CustomMath

end


What we are doing there is declaring the pi, sin, cos and tan functions so that they are accessible from our Sass stylesheets, and then delegating all the real work to the Math.* functions in Ruby.

Let’s invoke the sass command with our new file:

\$ sass --require sass_math.rb input.scss output.css


Boom. It’s not as fun as writing your own functions, but it’s much more efficient. Posted on by Daniel Perez Alvarez. Got any comments or suggestions? Send me a tweet or an email.