Animating CSS Gradients, using only CSS
You can’t animate gradients on the web.
Yes, I know this statement isn’t true, but if you are a front-end developer working in the CSS3-era of the web, then you probably know that I’m at least half right.
I am talking about animating between two background gradients using CSS animations, such as the transition property, as illustrated below:
Now, there are plenty of workarounds out there, but usually they employ either SVG or JavaScript to go that extra 5 yards which CSS on it’s own falls short of. I will touch on these very briefly before showing you a simple, pure CSS solution that in my opinion is the quickest and most elegant way to solve this problem. If you’re antsy to see it in action, here’s a link to the Pen (which is also embedded below). Also, the folks over at Webucator made a wonderful walkthrough video for this article, because they are awesome.
JavaScript
Admit it, doing this job with JavaScript just feels wrong. It’s because gradients are styles, created in the realm of CSS, and so your dealings with them should remain there. You could argue that heavy animation should be a JavaScript job and not a CSS job, and if that’s what you’re doing with gradients then by all means, there are plenty of options out there. But realistically, for a simple transition between two gradients, you needn’t be writing a script.
SVG
SVG, as we all know, is the golden goose of graphics and animation on the web. If you need something done right, you go to SVG. Including gradients. Now, I’ll be the first to admit they aren’t quite as accessible to developers who would usually never touch them. But SVG’s are an important web technology that aren’t going away anytime soon, so dabbling here and there pays dividends in the long run. Using SVG, you can pull off what we’re trying to do and a whole lot more.
CSS
Now we’re back at CSS which, by the way, is what we’re using to style the rest of our website, is capable of impressive (if often simple) animations, and is familiar to us already. Let’s see if we can do this using only CSS (we can cause we’re awesome).
Quickly, what is a CSS gradient? How is it defined? Something like this (no vendor prefixes because 90% of browsers support the following syntax):
background: linear-gradient( #fff, #ccc );
Okay so gradients are created using the linear-gradient value (or radial- or repeating-). This is important. Gradients are just a value of the background property. If you dig even deeper, you’ll find that what is actually being set is the background-image property. Herein lies the problem with animating CSS gradients. Let’s go visit our friends over at W3Schools for answers:
Not animatable. This is why animating CSS gradients is an issue. Just like you can’t smoothly transition from one background image to another, you can’t smoothly transition from one CSS gradient to another. It’s binary; either one background-image is shown or the other.
What you can do, however, is have two gradients, stacked on top of each other, and then transition the opacity of the top element. The opacity property is ubiquitous and animatable, making it perfect for this job.
The method I am going to show you makes use of the :before psudo-element. You, of course, could add an additional element to your HTML if you’d like, but for the sake of keeping our markup clean and concerns separated, I prefer to use the :before element.
Let’s start with our main element. Let’s give it some basic styles and a gradient (once again prefix-free):
The important part here is that there is an element with a gradient, and it’s z-index is set to 100. I avoid z-index as a rule, as it adds another dimension of complexity that otherwise would not need to be present in most projects. The only reason I’m setting it here is so that any content inside our element (such as text) will appear in front of the psudo-element, which will have a background gradient that obstructs the view of the element’s content.
Speaking of, lets style out our psudo-element:
The takeaway here is that the :before element has the opposite (or whatever) gradient that we want to transition to, it’s z-index is set to negative and it’s opacity is set to zero.
We’ll wrap this up by adding our transitions and hover states:
Now when we hover over our .button, the :before element (with it’s opposite/different gradient) fades in smoothly. Pretty neat trick, eh? You can check out a live example in the CodePen example below:
What I’ve done above is made this effect into a reusable mixin, and then applied it to the <body> element as well to create an interesting effect whenever the window is hovered over. I’ve created mixins for both Less and Sass if you’d like to drop this somewhere and test it out:
I still like this solution more than other ones, because CSS is my homeboy. Having said that, I still feel like SVG is probably the “correct” solution. Sometimes animating between two gradients in CSS can cause the color to get kind of muddy mid-transition in some browsers, something that I believe SVG would be a lot better at handling. But I mean you can’t beat the ease of CSS, so it’s damn temping to take this route.
Let me know how this works for you, as well as any suggestions you may have to build upon what’s been done here.
Special Thanks to Webucator
I would like to thank Brian and the team from Webucator for producing the following video that discusses this technique. You should definitely go watch their other videos and follow them on the Twitter. Thanks guys!