acos()

Juan Diego Rodríguez on Updated on

The CSS acos() trigonometric function takes a number and returns its inverse for the cos() function. Specifically, it takes a number between -1 and 1 and returns the angle between  and 180° (in radians) that, when passed to cos(), produces that number.

.element {
  --acos: calc(acos(var(--i) / var(--count)));

  /* acos() returns an angle, so to graph it, I used it as a number  */
  transform: translateY(calc(-1.5px * var(--int-acos)));
}

The acos() function is defined in the CSS Values and Units Module Level 4 specification

Syntax

acos( <calc-sum> )

The acos() function takes in any calculation that must resolve to a number between -1 and 1, otherwise it returns as not a number, NaN, and makes the declaration invalid.

Arguments

/* it only takes numbers between -1 and 1 */
rotate: acos(0.3);
rotate: acos(-0.5);

/* it undoes cos() from 0deg to 180deg */
rotate: acos(cos(45deg)); /* equal to 45deg */
rotate: acos(cos(120deg)); /* equal to 120deg */

/* we can do inner calculations */
rotate: acos(sqrt(2) / 2);
rotate: acos(sqrt(3) / 2);

As you can see, acos() takes a cosine’s result between -1 and 1, and undoes it to get the original angle between  and 180° in radians.

What’s acos()?

In case you haven’t checked the entry for cos(), which I strongly recommend you do, then you gotta know that it takes an angle and returns the cosine for that angle, which is a number between -1 and 1. To be more precise, remember that if we are in a unit circle (a circle of radius one), then we can go around it by an angle, measured from the positive x-axis. So given that angle, cos() returns the x-coordinate in the unit circle:

While cos() takes an angle and returns a number between -1 and 1acos() does the opposite: it takes a number between -1 and 1 and returns the angle used in cos(). Mathematically, this translates to… 

y = acos(x); x = cos(y)

You’ll notice from the last demo that cos() goes through three important values:

  • 0 at 90° and 270°
  • -1 at 180°
  • 1 at 

This means we can get 0 in two different ways. In fact, if we keep spinning around the unit circle, all values repeat infinitely. This brings up a conflict: which value should acos(0) return? 90° or 270°? To avoid this kind of inconsistency, acos() output is capped between 0deg and 180deg. As you can see from this graph:

acos() graphed

Basic usage

Firstly, let’s see how acos() works in CSS. Imagine we have a progress --progress that goes from -1 to 1:

:root {
  --progress: 0.5;
}

Using acos() we could map each value from --progress to an angle between 0deg and 180deg. Although it would be better to say that it’s between 180deg and 0deg, since:

  • -1 at 180°
  • 1 at 

Knowing this, we could rotate an element by an angle given by acos() as --progress progresses.

.element {
  rotate: acos(var(--progress));
}

However, if we use acos() as it is, then we are only able to rotate elements between 180deg and 0deg. I think we can both agree that it would be way more useful to get values all around 360deg. Luckily, we can transform acos() range by multiplying its output by 2 so that values are between 360deg and 0deg.

.element {
  rotate: calc(acos(var(--progress)) * 2);
}

The last thing we would like to do is avoid working with a --progress input from -1 to 1. It would be way more common to work with a progress variable from 0 to 1. To do so, we can map an input between -1 to 1 to an input from 0 and 1 like so:

.element {
  rotate: calc((acos((var(--progress) * 2 - 1)) * 2));
}

Which works similarly to how we mapped the angles: we multiply by 2 to get inputs between 0 and 2, and then shift them by -1 so they are between -1 and 1.

Using acos() as a Bezier curve

You may be wondering why we did all the last transformations to acos(). Well, first, acos() as kind of useless as-is since we can only invert numbers from -1 to 1 and get angles between 0deg and 180deg. We can wiggle out of these limitations with the last transformation.

However, you may also ask in which situations we could use acos(), since converting a progress variable between 0 and 1 to an angle between 0deg and 360deg is usually as easy as:

.element {
  rotate: calc(var(--progress) * 360deg);
}

Notice that the last snippet rotates the element at a linear rate. If we want to make it spin at a different rate, we could use a Bezier curve (which, admittedly, is simpler), or we could take the fun route and notice that our last acos() transformation is a Bezier curve! To see that, let’s graph it again with the transformations:

acos() transformed graphed

Remember that a Bezier curve takes an input between 0 and 1 and returns and output between 0 and 1 too, and our acos() transformation also receives an input between 0 and 1 but already outputs an angle between 360deg and 0deg!

So, for example, we could make spin animations with a little rush at the end using acos(). To do so, we’ll firstly set up a progress @property and animate it between 0 to 1:

@property --progress {
  syntax: "<number>";
  initial-value: 0;
  inherits: false;
}

@keyframes progress {
  from {
    --progress: 0;
  }

  to {
    --progress: 1;
  }
}

We define --progress with @property so we can interpolate it with CSS animations.

Then we can spin elements following acos() curve:

.loading {
  --angle: calc((acos((var(--progress) * 2 - 1)) * 2));

  background: radial-gradient(#262325 50%, transparent 50%), 
              conic-gradient(#fffa var(--angle), transparent var(--angle));
  animation: progress 5s linear infinite;
}

Specification

The acos() function is defined in the CSS Values and Units Module Level 4 specification, which is currently in Editor’s Draft.

Browser support

More information