The following is a guest post by Ana Tudor. She is passionate about experimenting and learning new things. Also she loves maths and enjoys playing with code.
I had no idea how powerful CSS gradients could be until late 2011, when I found the CSS3 Patterns Gallery made by Lea Verou. The idea that you can obtain many shapes using just gradients was a starting point for many CSS experiments I would later do.
Recently, while browsing through the demos on CodePen, I came across a CSS3 Color Wheel and thought hey, I could do it with just one element and gradients. So I did and the result can be seen here. And now I’m going to explain the reasoning behind it.
Breaking it down
The wheel - or you can think of it as a pie - is first split horizontally into two halves and then each half is split into five slices, so there are ten slices in total. Which means that the central angle for each slice is
/10 = 36°.
The pen below shows graphically how to layer the multiple backgrounds. It also has a pause button so that the infinite animation doesn’t turn into a performance problem.
For both the original pen and this helper demo, the interesting part is this one:
background: linear-gradient(36deg, #272b66 42.34%, transparent 42.34%),
linear-gradient(72deg, #2d559f 75.48%, transparent 75.48%),
linear-gradient(-36deg, #9ac147 42.34%, transparent 42.34%) 100% 0, linear-gradient(
) 100% 0,
linear-gradient(36deg, transparent 57.66%, #e1e23b 57.66%) 100% 100%, linear-gradient(
) 100% 100%,
linear-gradient(-36deg, transparent 57.66%, #662a6c 57.66%) 0 100%, linear-gradient(
) 0 100%, #43a1cd linear-gradient(#ba3e2e, #ba3e2e) 50% 100%;
background-size: 50% 50%;
We first specify the nine gradient backgrounds, their positioning and the
background-color using the shorthand
The background shorthand
For anyone who doesn’t remember, the background layers are listed from the top one to the bottom one and the
background-color is specified together with the bottom layer. A background layer includes the following:
background-position is not specified, then the
background-size isn’t specified either. Also, since
background-clip both need the same kind of value (that is, a box value like
content-box), then, if there is only one such value, that value is given to both
background-clip. Other than that, any value except the one for
background-image can be missing and then it is assumed to be the default.
Since we have nine background layers and we want to have the same non-default values for
background-size for all of them, we specify these outside the shorthand so that we don’t have to write the same thing nine times.
In the case of
background-size, there is also another reason to do that: Safari doesn’t support
background-size inside the shorthand and, until recently (up to and including version 17), Firefox didn’t support that either. Also, two values should be always given when the
background-image is a gradient, because giving it just one value is going to produce different results in different browsers (unless that one value is 100%, in which case it might as well be missing as that is the default).
background-color is set to be a light blue (
#43a1cd) and then, on top of it, there are layered nine non-repeating (
background-repeat: no-repeat for all) background images created using CSS gradients. All nine of them are half the
width and the
height of the element (
background-size: 50% 50%).
The bottom one - horizontally centred (
50%) and at the bottom (
100%) - is really simple. It’s just a gradient from a firebrick red to the same color (
linear-gradient(#ba3e2e, #ba3e2e)), so the result is simply a solid color square.
The other eight are gradients from
transparent to a solid color or from a solid color to
transparent. Four of them look like double slices, having a central angle of
2*36° = 72°, but half of each such double slice gets covered by another single slice (having a central angle of
A few things about linear gradients
In order to better understand gradient angles and how the
% values for color stops are computed, let’s see how a linear gradient is defined. Hopefully, this demo that lets you change the gradient angle helps with that - just click the dots.
The gradient angle is the angle - measured clockwise - between the vertical axis and the gradient line (the blue line in the demo). This is for the new syntax, which is not yet supported by WebKit browsers (however, this is going to change). The old syntax measured angles just like on the trigonometric unit circle (counter-clockwise and starting from the horizontal axis).
Note: coming from a mathematical background, I have to say the old way feels more natural to me. However, the new way feels consistent with other CSS features, like rotate transforms, for which the angle values are also clockwise.
What this means is that we (almost always) have different angle values in the standard syntax and in the current WebKit syntax. So, if we are not using something like -prefix-free (which I do almost all the time), then we should to be able to compute one when knowing the other. That is actually pretty simple. They are going in opposite directions, so the formula for one includes the other with a minus sign. Also, there is a
90° difference between them so this is how we get them:
newSyntax = 90° - oldSyntax;
oldSyntax = 90° - newSyntax;
Note: if no gradient angle or destination side is specified (for example,
linear-gradient(lime, yellow)), then the resulting gradient is going to have a gradient angle of
All the points on a line that is perpendicular on the gradient line have the same color. The perpendicular from the corner in the quadrant that’s opposite to the quadrant of the angle is the
0% line (the crimson line in the demo) and its intersection with the gradient line is the starting point of the gradient (let’s call it
S). The perpendicular from the opposite corner (the one in the same quadrant as the gradient angle) is the
100% line (the black line in the demo) and its intersection with the gradient line is the ending point of the gradient (let’s call it
In order to compute the
% value of any point
P, we first draw a perpendicular on the gradient line starting from that point. The intersection between the gradient line and this perpendicular is going to be a point we’ll name
I. We now compute the ratio between the lengths of
SE and the
% value for that point is going to be
100% times that ratio.
Putting it all to work
Now let’s see how we apply this for the particular case of the rainbow wheel.
Let’s first consider a gradient that creates a single slice (one with a central angle of
36°). This is a square image (see below), with a blue slice having an angle of
36° in the lower part. We draw the horizontal and vertical axes through the point
O at which the diagonals intersect. We draw a perpendicular from that point to the line that separates the dark blue part from the transparent part. This is going to be the gradient line. As it can be seen, there is a
36° angle between the vertical axis and the gradient line, so the angle of the gradient is
We now draw a perpendicular from the corner of the square in the quadrant that is opposite to the one in which the gradient angle is found. This is the
0% line. Then we draw a perpendicular from the corner of the square in the same quadrant (
Q I) as the gradient angle - this is the
The intersection of the diagonals of a square splits each one of them into two, so
BO are equal. The
AOS angles are equal, as they are vertical angles. Moreover, the
AOS triangles are right triangles. All these three mean that the two triangles are also congruent. Which in turn means that
EO are equal, so the length of
SE is going to be twice the length of
EO or twice the length of
Note: before moving further, let’s go through a couple of trigonometry concepts first. The longest side of a right-angled triangle is the one opposing that right angle and it’s called the hypotenuse. The other two sides (the ones forming the right angle) are called the catheti of the right triangle. The sine of an acute angle in a right triangle is the ratio between the cathetus opposing that angle and the hypotenuse. The cosine of the same angle is the ratio between the adjacent cathetus and the hypothenuse.
Computing the length of
EO in the right triangle
BOE is really simple. If we take the length of the side of the square to be
a, then the length of the half diagonal
BO is going to be
BOE angle is equal to the difference between the
BOM angle, which is
45°, and the
EOM angle, which is
36°. This makes
BO is also the hypotenuse in the right triangle
BOE, the length of
EO is going to be
(a*sqrt(2)/2)*cos9°. Which makes the length of
We now draw a perpendicular from
A to the
ASID is a rectangle, which means that the length of
SI equals the length of
AD. We now consider the rectangular triangle
APD. In this triangle,
AP is the hypotenuse and has a length of
a. This means that
AD is going to have a length of
SI is equal to
AD, so it also has a length of
Since we now know both
SE, we can compute their ratio. It is
sin36°/(sqrt(2)*cos9°) = 0.4234. So the
% value for the color stop is
In this way, we’ve arrived at:
linear-gradient(36deg, #272b66 42.34%, transparent 42.34%)
% values for the other background layers is done in the exact same manner.
Automating all this
By now, you’re probably thinking it sucks to do so many computations. And it must be even worse when there are more gradients with different angles…
Even though for creating the rainbow wheel experiment I did compute everything on paper… I can only agree with that! This is why I made a really basic little tool that computes the
% for any point inside the gradient box. You just need to click inside it and the
% value appears in a box at the bottom center.
You can change the dimensions of the gradient box and you can also change the gradient itself. It accepts the newest syntax for linear gradients, with angle values in degrees,
to <side> values or no value at all for describing the direction of the gradient.
CSS gradients are really powerful and understanding how they work can be really useful for creating all sorts of imageless textures or shapes that would be difficult to obtain otherwise.