Animating a Hue around the Color Wheel with Houdini
Even though the
hsl() color function allows us to specify hue, saturation, and lightness levels individually, there is no straightforward way to animate one value independently of the others. We will explore how color interpolation works on the web, look at our options to animate a hue around a color wheel, and see how Houdini gives us a mechanism to do this.
The basics of
The beauty of defining a color with
hsl() is that you can start with a hue and then tweak it. If you want a reddish color you use a hue near
0, or a cyan color is on the opposite side of the wheel at
180 (the values represent degrees in a circle, so the range of different values are from 0 to 360). The other two values are percentages from
100%. Saturation shows how full the color is at
100% or grayed out more as you approach
0%. Lightness at
0% is always black,
100% is always white, and
50% is a good default for getting a bright color.
Interpolation is effectively how the values between a start and end state are determined during an animation. Browsers are very efficient at determining the values between two numbers such as filling in an opacity animation from
Determining what colors to show between two color states in an animation is actually also straightforward for a browser to accomplish, as colors boil down to a set of numbers.
With RGB colors like
rgb(255,170,187) we have three sets of numbers that can be interpolated individually to determing a color value at each step in the animation. So if we want to move from a magenta (
rgb(255,0,255)) to a cyan (
rgb(0,255,255)) the R value will animate from 255 to 0, the G value will go from 0 to 255, while the B value stays at 255. This process is also how the inbetween values are determined for gradients.
Every step of this transition is still very much a blend of these two colors. With
hsl() we get something a little more interesting where you can change a single value (the hue) and potentially get every color of the rainbow. Since we were able to interpolate each of the three RGB parts, you might think the three individual parts of the
hsl() will interpolate on their own as well and we could actually rotate around the color wheel simply by animating from
This red block is technically animating, I promise. However, regardless how you define your color, CSS always animates between its computed values in RGB in the sRGB color space. So even though we used HSL in the previous example, it technically is doing an animation from
#ff0000. You can edit the example to instead go from a hue of
0 to a hue of
180 (cyan) to see it doing the same animation you would find by going
So whether you use named colors, RGB, HSL, or future methods like
hwb(), the color stops are effectively converted to RGB and then have the interpolation applied.
Is it possible to animate a hue change only?
CSS Filters and
It is! Sort of!
CSS Filters have been well supported for several years now across the board, including non-Chromified-Edge. Most people tend to lean on filters for the
blur() function, but there are several other options including
This will take the current hue of your color and rotate it around the color wheel the angle of degrees you specify. So let’s try our animation again with a starting
hsl(0,100%,50%) and animate the hue to
We did it!*
With an asterisk!
My goal here was to keep the saturation and lightness consistent and then change only the hue from
360. The hue was accurately rotated around a color wheel, but if you look closely at the color changes, we should see colors very similar to the perimeter of the circle in the first example of this article, where we can very clearly see each bright color from a rainbow — red, orange, yellow, green, blue, indigo, and violet. This animation clearly has a red, green, and blue, but everything in between is much darker and muddier than I’d expect.
That is because
hue-rotate() does not use the same color wheel as you get with
hsl() (as Amelia Bellamy-Royds wonderfully explains in this blog comment). It gets complicated with color spaces and the fact that
hue-rotate uses a combination of hue, saturation, and luminance instead of lightness.
So this gets us a more interesting color animation, but it is not a rainbow animation at the same saturation and lightness. We can approximate a true hue rotation by using multiple keyframes with different colors. For example we get a little closer if we set an animation with three states of
hsl(240,100%,50%). We get even closer as we do five keyframes and ten keyframes. With 36 frames we get an approximation that is very close to the desired result.
The Houdini Rotate
If you are in a browser that supports the Houdini Properties and Values API specification, you will see an extra animation in the previous example (try a Blink browser with Experimental Web Platform Features flag enabled or the latest Safari Technology Preview). That gives us the actual behavior I desired as it accurately keeps the saturation and lightness in place and interpolates the hue value between
As we can use Custom Properties to individualize the parts of a value, we can work with a base
hsl(var(--hue),100%,50%) color and modify our
--hue to go from
360. Custom Properties themselves are not animatable out of the box, but Houdini allows the web author to provide a syntax for a specific custom property which tells it the rules to interpolate the property’s value.
With the following CSS, we are changing the hue from
--hue as a number, and so it can follow the interpolation rules for numbers.
And with that we get a true hue rotation that maintains the same saturation and lightness found in our
<percentage> syntax and an initial value that is a percentage, such as