Rengga Dev – Red + Blue = Purple… right? Is there some way to express that in CSS? Well, not easily. There is a proposal draft for a color-mix
function and some degree of interest from Chrome, but it doesn’t seem right around the corner. It would be nice to have native CSS color mixing, as it would give designers greater flexibility when working with colors. One example is to create tinted variants of a single base color to form a design palette.
But this is CSS-Tricks so let’s do some CSS tricks.
We have a calc()
function in CSS for manipulating numbers. But we have very few ways to operate directly on colors, even though some color formats (e.g. hsl()
and rgb()
) are based on numeric values.
Mixing colors with animation
We can transition from one color to another in CSS. This works:
div { background: blue; transition: 0.2s; } div:hover { background: red; }
And here’s that with animations:
div { background: blue; transition: 0.2s; } div:hover { animation: change-color 0.2s forwards; } @keyframes change-color { to { background: red; } }
This is an keyframe animation that runs infinitely, where you can see the color moving between red and blue. Open the console and click the page — you can see that even JavaScript can tell you the current color at any exact point in the animation.
So what if we pause the animation somewhere in the middle? Color mixing works! Here is a paused animation that is 0.5s
through it’s 1s
duration, so exactly halfway through:
We accomplished that by setting an animation-delay
of -0.5s
. And what color is halfway between red and blue? Purple. We can adjust that animation-delay
to specify the percentage of two colors.
This works for Chromium core browsers and Firefox. In Safari, you must change animation-name to force browser to recalculate the animation progress.
This trick can also be used for adding alpha channel to a color, which is typically useful for theming.
Getting the mixed color to a CSS custom property
This is a neat trick so far, but it’s not very practical to apply an animation on any element you need to use a mixed color on, and then have to set all the properties you want to change within the @keyframes
.
We can improve on this a smidge if we add in a couple more CSS features:
- Use a
@property
typed CSS custom property, so it can be created as a proper color, and thus animated as a color. - Use a Sass
@function
to easily call keyframes at a particular point.
HTML
<div id="inputs"> <input type="color" value="#FF0000" oninput="result.style.setProperty('--from-color', this.value)"> <input type="range" min="0" max="1" value="0.5" step="0.01" oninput="result.style.setProperty('--percent', this.value)"> <input type="color" value="#0000FF" oninput="result.style.setProperty('--to-color', this.value)"> </div> <div id="result" style="--from-color: #FF0000; --to-color: #0000FF; --percent: .5"> Some text. <br><br> <svg viewBox="0 0 640 512" width="60" title="biking"> <path d="M400 96a48 48 0 1 0-48-48 48 48 0 0 0 48 48zm-4 121a31.9 31.9 0 0 0 20 7h64a32 32 0 0 0 0-64h-52.78L356 103a31.94 31.94 0 0 0-40.81.68l-112 96a32 32 0 0 0 3.08 50.92L288 305.12V416a32 32 0 0 0 64 0V288a32 32 0 0 0-14.25-26.62l-41.36-27.57 58.25-49.92zm116 39a128 128 0 1 0 128 128 128 128 0 0 0-128-128zm0 192a64 64 0 1 1 64-64 64 64 0 0 1-64 64zM128 256a128 128 0 1 0 128 128 128 128 0 0 0-128-128zm0 192a64 64 0 1 1 64-64 64 64 0 0 1-64 64z" /> </svg> </div>
SCSS
@property --result-color { syntax: "<color>"; initial-value: transparent; inherits: false; } @keyframes kf-color-mix { 0% { --result-color: var(--from-color) } 100% { --result-color: var(--to-color) } } @function color-mix($percent) { @return kf-color-mix 1s calc(-1s * #{$percent}) linear forwards paused; } #result { height: 20px; margin-top: 10px; padding-top: 10px; animation: color-mix(var(--percent)); border-top: 20px solid var(--result-color); color: var(--result-color); fill: var(--result-color); } body { margin: 1em; width: 300px; } #inputs { display: flex; align-items: center; } [type=color] { width: 50px; padding: 1px 2px; } [type=range] { margin: 0 10px; flex: 1; }
JS
window.result = document.getElementById('result'); result.onclick = function() { console.log(getComputedStyle(this).backgroundColor); };
Now we still need to call animation, but the result is that a custom property is altered that we can use on any other property.