Trigonometry in CSS and JavaScript: Getting Creative with Trigonometric Functions
In part 1 we got an overview of trigonometry and learnt how we can use trigonometric functions in Sass. But for dynamic variables, we would be wise to move our calculations into JavaScript. Let’s take a look at an example that’s slightly more complex than clipping a simple triangle.
This article is the 2nd part in a series on “Trigonometry in CSS and JavaScript”:
- Introduction to Trigonometry
- Getting Creative with Trigonometric Functions (this article)
- Beyond Triangles
In the following demo we have a square-based pyramid, built with CSS 3D transforms. Using the slider, we can change the length of the sides of the pyramid, which results in changes to the overall height, and the angle of the sloping sides.
See the Pen Pyramids by Michelle Barker (@michellebarker) on CodePen.dark
To recalculate the angle at which the sides are rotated every time the input value changes, we need trigonometry. In order to do that, we can take a cross-section of the pyramid from the side, and visualize it as a triangle.
We can see that, just like our equilateral triangle in the previous article, the cross-section of our pyramid can be broken up into two right-angled triangles. (This time the shape of the cross-section is an isosceles triangle — a triangle that has two sides of equal length.)
To create the shapes for the base and sides of the pyramid, we can set the width and initial height, and use clip-path
to clip the triangular shape of the sides.
.shape__base {
--w: 10rem;
width: var(--w);
height: var(--w);
}
.shape__side {
width: var(--side);
height: var(--h, 20rem);
clip-path: polygon(50% 0, 100% 100%, 0 100%);
}
I’m using custom properties here because they allow us to easily reuse identical values. I’m setting a default value for the --h
custom property for the height
value of the shape side, as we’ll change this value later on with JavaScript. (This is the value we’ll get from the slider.)
Going back to our cross-section diagram, we can see that our known values are the opposite side (which will be half of our --w
variable) and the hypotenuse (the --h
variable). What is unknown is the angle at which we need to rotate the sides so that they meet in the middle.
If we imagine the side of the pyramid originates from a starting position in the center, the angle we need to calculate is the one at the top of the triangle. We can think of it as being a bit like leaning a ladder against a wall. The angle between the ladder and the wall is the one we need to calculate.
Again, we can use custom properties in our CSS to set some transform values. Each side will have the same rotateX()
value (the angle we’re going to calculate), but different rotateY()
values, as they’ll be rotated around the pyramid (represented by the --ry
custom property here):
.shape__side {
transform-origin: top center;
transform:
rotateY(var(--ry, 0))
rotateX(var(--angle, 15deg));
}
.shape__side:nth-child(2) {
--ry: 90deg;
}
.shape__side:nth-child(3) {
--ry: -90deg;
}
.shape__side:nth-child(4) {
--ry: 180deg;
}
Calculating angles
In the previous article we saw how we can calculate the length of any side of a right-angled triangle if we know the angle, but how about calculating the angle itself? For that, we need to rearrange our equations.
We know the opposite side and the hypotenuse, which indicates we need to use the Sine function. Dividing the opposite by the hypotenuse gives us sin(ϴ):
sin(angle) = o / h
Therefore the angle is calculated by the inverse Sine (or Arcsine) of the opposite divided by the hypotenuse:
Math functions
We can use JavaScript math functions for this. Let’s create a function to call whenever the input changes, and update the --h
(for the hypotenuse) and --angle
custom properties. To get the Arcsine value we use Math.asin()
:
const shape = document.querySelector('.shape')
const input = document.querySelector('[data-slider]')
const setAngles = () => {
const o = shape.clientWidth / 2
const h = input.value
const angle = Math.asin(o / h)
shape.style.setProperty('--h', `${h}px`)
shape.style.setProperty('--angle', `${angle}rad`)
}
input.addEventListener('input', setAngles)
Radians versus degrees
You might notice that we’re setting the --angle
custom property value in radians, not degrees. Unless you’re a mathematician, there’s a good chance you usually think of angles in terms of degrees, rather than radians. A radian can be visualized as the length of the radius of a circle wrapped around the circumference. There are 2pi radians in a circle.
The Math.asin()
function gives us the angle in radians, and radians are perfectly legitimate units in CSS, so this will work just fine. But if you prefer to set the value in degrees, we can convert them with a simple function:
const radToDeg = (radians) => {
return radians * (180 / Math.PI)
}
In the demo I’m also rounding the resulting value to two decimal places with toFixed()
:
const setAngles = () => {
const o = shape.clientWidth / 2
const h = input.value
const radians = Math.asin(o / h)
const angle = radToDeg(radians).toFixed(2)
shape.style.setProperty('--h', `${h}px`)
shape.style.setProperty('--angle', `${angle}deg`)
}
Now the angles of the sides of our pyramid will be recalculated every time we move the slider to change the length of the sides.
Get creative
Using the same method, we could even create a bunch of pyramids of random heights, by changing a single custom property:
See the Pen Pyramids by Michelle Barker (@michellebarker) on CodePen.dark
Here’s another creative example of trigonometry in action: A paper snowflake maker, where the user can drag the handles to clip out segments of a triangle to generate the snowflake pattern. The clip path coordinates were calculated using trigonometric functions.
See the Pen Snowflakes with clip-path trigonometry by Michelle Barker (@michellebarker) on CodePen.dark
In the next article we’ll see how trigonometry affords us even more creative possibilities when combined with JS, by enabling us to plot polygons and more complex shapes.