I stumbled into an interesting problem the other day. I wanted to animate an element with a random animation-duration
. This was the non-randomized starting point:
See the Pen Random numbers CSS #1 by Robin Rendle (@robinrendle) on CodePen.
This is the CSS I wrote to make the animation:
@keyframes flicker {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
#red {
animation: flicker 2s ease alternate infinite;
}
So far so good. But no randomization happening there, that’s a fixed 2 seconds.
I wanted that animation time of 2 seconds to be random. I wanted to essentially write:
.element {
animation: flicker $randomNumber alternate infinite;
}
Where $randomNumber
is randomized programatically.
CSS preprocessors like Sass do offer a random() function.
$randomNumber: random(5);
.thing {
animation-duration: $randomNumber + s;
}
That might be perfect for you, it wasn’t quite perfect for me. Random numbers generated during preprocessing have a big caveat:
random in Sass is like randomly choosing the name of a main character in a story. it's only random when written. it doesn't change.
— jake albaugh (@jake_albaugh) December 29, 2016
In other words, as soon as the CSS is processed, randomization is over. That number is locked at that value forever (i.e. until the preprocessor runs again).
It’s not like a random number in JavaScript (e.g. Math.random()
) where that random number is generated when the JavaScript runs.
So after sighing as obnoxiously loudly as I could I realized that this would actually be the perfect opportunity to use native CSS variables (custom properties)! By themselves, they can’t do random numbers easier, but as we’ll see, they can still help us.
If you’re not familiar with them, then not to worry. Effectively they’re native variables built into the CSS language itself, but they’re different from the sort of variables that you might be familiar with from a preprocessor like Sass or Less. Chris listed many of the benefits a while back:
- You can use them without the need of a preprocessor.
- They cascade. You can set a variable inside any selector to set or override its current value.
- When their values change (e.g. media query or other state), the browser repaints as needed.
- You can access and manipulate them in JavaScript.
That last bit is what’s important to us. We’re going to generate the random number in JavaScript, then move it over to CSS via custom properties.
Set one is to create the CSS custom property we need, with a default value (useful in case the JavaScript we write in a moment fails for any reason):
/* set the default transition time */
:root {
--animation-time: 2s;
}
Now we can use that variable in our CSS like this:
#red {
animation: flicker var(--animation-time) ease alternate infinite;
}
Undramatically, we’re exactly where we started. But although this demo now looks exactly the same as our previously animated SVG, this one is using CSS variables instead. You can test that everything is working by just changing the variable in the CSS and watch as the animation updates.
Now we’re all set up to access and manipulate that custom property via JavaScript.
var time = Math.random();
From here we can find the red circle in the SVG and change the --animation-time
CSS variable via the setProperty
method:
var red = document.querySelector('#red');
red.style.setProperty('--animation-time', time +'s');
And here it is! A randomly generated number in CSS which is being applied to an SVG animation:
See the Pen Random numbers CSS #3 by Robin Rendle (@robinrendle) on CodePen.
This is a step forward because the random number is generated when the JavaScript runs, so it’s different every time. That’s pretty close to what we wanted, but let’s make this a little bit more difficult still: let’s randomize that animation-duration
periodically as it’s running.
Fortunately, we have JavaScript to work with now, so we can update that custom property whenever we want to. Here’s an example where we update the animation-duration
every second:
var red = document.querySelector('#red');
function setProperty(duration) {
red.style.setProperty('--animation-time', duration +'s');
}
function changeAnimationTime() {
var animationDuration = Math.random();
setProperty(animationDuration);
}
setInterval(changeAnimationTime, 1000);
That’s exactly what I was after:
See the Pen Random numbers CSS #4 by Robin Rendle (@robinrendle) on CodePen.
It’s useful to remember that CSS variables (custom properties) support is still a little patchy, so beware. Although what we could do is progressively enhance this animation like so:
#red {
animation: flicker .5s ease alternate infinite;
animation: flicker var(--animation-time) ease alternate infinite;
}
If CSS variables aren’t supported then we’ll still get some kind of animation being shown, even if it isn’t precisely what we want.
It’s worth noting that CSS variables aren’t the only possible way to randomize the animation-duration. We could access the DOM element via JavaScript and apply the random value directly into the style
:
var red = document.querySelector('#red');
red.style.animationDuration = Math.floor(Math.random() * 5 + 1) + "s";
We could even wait for the animation to finish before setting a new duration, if we wanted:
var red = document.querySelector('#red');
function setRandomAnimationDuration() {
red.style.animationDuration = Math.floor(Math.random() * 10 + 1) + "s";
}
red.addEventListener("animationiteration", setRandomAnimationDuration);
Just to sprinkle one more possibility in here, you could also do this with EQCSS.
@element '#animation' {
.element {
animation-duration: eval('rand')s;
}
}
var rand = Math.random();
EQCSS.apply();
Here’s that bare minimum demo and an actual demo.
Do you wish randomization was available right in CSS itself? I’m not sure if there is any talk of that. Even if there was, we would likely have to wait quite a while to actually use it. Along those lines, Philip Walton recently wrote how difficult it would be to write a true polyfill for random numbers in CSS. Much easier to handle in JavaScript!
What I’s really want in CSS is functions (not just random numbers, which have limited use-cases) and a way to define them. Let’s see what Houdini has to offer, though!
Using animation events comes to mind immediately after seeing the
setTimeout
method. Although, for some reason, listening foranimationiteration
results in erratic flickering both in Firefox and Chrome. https://codepen.io/jtojnar/pen/bgpNMKI found the exact same thing when I was playing with it. Solution eludes me.
I’m surprised you changed the –animation-time in the #red rather than in the :root. Potentially other uses of –animation-time might want the same randomization. Only #red sees the randomization, by changing it there.
If you intend for only #red to see it, then defining it there would be adequate, without defining it at :root, or am I missing something?
It goes to show that interesting aspect of custom properties: scope!
Hi,
I just had a different approach in mind. Not completely random, but still a valid idea.
You could as well have lets say 10 css classes with different durations. And just add a random one of these with JS, or even serve a random one with PHP to your HTML.
It’s not a perfect random number in CSS, true, but it would still have the same effect, relying on less new features.
Another approach could be using multiple elements (with their animations) and the cicada principle:
See the Pen Random animation puere CSS by Kseso (@Kseso) on CodePen.
How do I get the randomization to work in iOS 10 Safari since caniuse says it does? I keep getting the same duration every interval.
Just been reading Lea Verou’s CSS secrets book where she makes random background widths using ‘The cicada principle’ https://www.sitepoint.com/the-cicada-principle-and-why-it-matters-to-web-designers/ which could apply here.