CSS animation tips and tricks
CSS animations make it possible to do incredible things with the elements that make up your documents and apps. However, there are things you might want to do that aren’t obvious, or clever ways to do things that you might not come up with right away. This article is a collection of tips and tricks we’ve found that may make your work easier, including how to run a stopped animation again.
Run an animation again
The CSS Animations specification doesn’t offer a way to run an animation again. There’s no magic resetAnimation() method on elements, and you can’t even just set the element’s animation-play-state to «running» again. Instead, you have to use clever tricks to get a stopped animation to replay.
Here’s one way to do it that we feel is stable and reliable enough to suggest to you.
HTML
div class="box">div> div class="runButton">Click me to run the animationdiv>
CSS
Now we’ll define the animation itself using CSS. Some CSS that’s not important (the style of the «Run» button itself) isn’t shown here, for brevity.
.runButton cursor: pointer; width: 300px; border: 1px solid black; font-size: 16px; text-align: center; margin-top: 12px; padding-top: 2px; padding-bottom: 4px; color: white; background-color: darkgreen; font: 14px "Open Sans", "Arial", sans-serif; >
@keyframes colorchange 0% background: yellow; > 100% background: blue; > > .box width: 100px; height: 100px; border: 1px solid black; > .changing animation: colorchange 2s; >
There are two classes here. The «box» class is the basic description of the box’s appearance, without any animation information included. The animation details are included in the «changing» class, which says that the @keyframes named «colorchange» should be used over the course of two seconds to animate the box.
Note that because of this, the box doesn’t start with any animation effects in place, so it won’t be animating.
JavaScript
Next we’ll look at the JavaScript that does the work. The meat of this technique is in the play() function, which is called when the user clicks on the «Run» button.
function play() document.querySelector(".box").className = "box"; requestAnimationFrame((time) => requestAnimationFrame((time) => document.querySelector(".box").className = "box changing"; >); >); >
This looks weird, doesn’t it? That’s because the only way to play an animation again is to remove the animation effect, let the document recompute styles so that it knows you’ve done that, then add the animation effect back to the element. To make that happen, we have to be creative.
Here’s what happens when the play() function gets called:
- The box’s list of CSS classes is reset to «box» . This has the effect of removing any other classes currently applied to the box, including the «changing» class that handles animation. In other words, we’re removing the animation effect from the box. However, changes to the class list don’t take effect until the style recomputation is complete and a refresh has occurred to reflect the change.
- To be sure that the styles are recalculated, we use window.requestAnimationFrame() , specifying a callback. Our callback gets executed just before the next repaint of the document. The problem for us is that because it’s before the repaint, the style recomputation hasn’t actually happened yet!
- Our callback cleverly calls requestAnimationFrame() a second time! This time, the callback is run before the next repaint, which is after the style recomputation has occurred. This callback adds the «changing» class back onto the box, so that the repaint will start the animation once again.
Of course, we also need to add an event handler to our «Run» button so it’ll actually do something:
.querySelector(".runButton").addEventListener("click", play, false);
Result
Stopping an animation
Removing the animation-name applied to an element will make it jump or cut to its next state. If instead you’d like the animation to complete and then come to a stop you have to try a different approach. The main tricks are:
- Make your animation as self-contained as possible. This means you should not rely on animation-direction: alternate . Instead you should explicitly write a keyframe animation that goes through the full animation in one forward repetition.
- Use JavaScript and clear the animation being used when the animationiteration event fires.
The following demo shows how you’d achieve the aforementioned JavaScript technique:
.slidein animation-duration: 5s; animation-name: slidein; animation-iteration-count: infinite; > .stopped animation-name: none; > @keyframes slidein 0% margin-left: 0%; > 50% margin-left: 50%; > 100% margin-left: 0%; > >
h1 id="watchme">Click me to stoph1>
const watchme = document.getElementById("watchme"); watchme.className = "slidein"; const listener = (e) => watchme.className = "slidein stopped"; >; watchme.addEventListener("click", () => watchme.addEventListener("animationiteration", listener, false), );
See also
Found a content problem with this page?
This page was last modified on Jul 7, 2023 by MDN contributors.
Your blueprint for a better internet.
How do I stop animation in HTML and CSS
I am trying to make an advent calendar for my website similar to THIS example. I have the basic layout of the calendar created and when I hover over one of the «tiles» on the calendar the animation starts and the tile swings open. The animation swings the tile in the fashion as if you are opening a door towards you and it swings open to 90 degrees. However, when the animation gets to 90 degrees, the tile is reset to its original closed position. How can I keep the door open while the user hovers over the tile and then swing the tile closed again when the user removes the cursor from the tile? When the tile is open I also need to have an image behind it that is a clickable link to another page on my site. I have set this in a but all that is displayed is the background color of my .tile div (background-color: #000;) and not the clickable image or even my .back div. Can anyone please help. I have attached my current CSS and HTML that shows how the tile is created and animated. Note: I do not need to handle touch events from mobile devices for this example. CSS
.tile < background-color: #000; color: #ffffff; border: 1px solid #220001; >/* Flip the tile when hovered */ .tile:hover .flipper, .tile.hover .flipper < -webkit-animation: example 2s; >@-webkit-keyframes example < from < -webkit-transform: perspective(500) rotateY(0deg); -webkit-transform-origin: 0% 0%; >to < -webkit-transform: perspective(500) rotateY(-90deg); -webkit-transform-origin: 0% 0%; >> .tile, .front, .back < width: 120px; height: 120px; >.flipper < position: relative; >.front, .back < backface-visibility: hidden; position: absolute; top: 0; left: 0; >/* Front Tile - placed above back */ .front < z-index: 2; transform: rotateY(0deg); background-color: #760805; >/* Back - initially hidden Tile */ .back
Is it possible to turn off all CSS animations in Chrome?
I have a weird issue where if I play an animation on one of my 3 monitors, YouTube videos on any other monitor crashes. I did fix this by disabling hardware acceleration in chrome://flags, but a new update in Chrome recently made the issue come back, and I haven’t found a way to fix it yet. Animations occur on places like Facebook («Someone is typing a comment. «) or basically any website with a animation-duration CSS property on something (spinners are probably the most used form of animations I guess). I can verify this simply by placing this CSS on any page:
Boom instantly all my videos play perfectly. No issues what so ever. I could add this to an userscript (or make a tiny extension), and I don’t think it would mess up too much, but I’m more interested in knowing if there’s a Chrome flag that can disable animations completely? I don’t know if animation-duration works for any animation.
3 Answers 3
/* turn off animation on all elements*/
From what I know Chrome has no such option.
But, I was able to make something similar using the Tampermonkey extension.
Simply add the following script to the extension:
// ==UserScript== // @name Animation Stopper // @description Stop all CSS animations on every website. // @author Ba2siK - https://stackoverflow.com/users/7845797 // @match *://*/* // @grant GM_addStyle // @run-at document-end // ==/UserScript== GM_addStyle(` *, *:before, *:after < transition-property: none !important; transform: none !important; animation: none !important; >` ); console.log("Animation Stopper ran successfully");
Make sure it’s enabled at the extensions bar
(Note: it won’t work on iframe elements.)
Btw, You can disable the window animation in chrome by adding the —wm-window-animations-disabled command-line flag.
Thanks for the reply. Seems like we have the same idea, as I posted mine one minute before yours haha. Yours won’t hit shadow DOM elements, unfortunately. The «wildcard selector» * does hit the :before and :after elements as well, as I tested before coming up with my own solution, so no need for those selectors it seems like
according to this question, the universal selector * does not affect pseudo-elements. And since you want to improve your performance, I think that disabling also Chrome’s animation would also help.
Ah, so it has to do with inheritance. Makes sense. Chrome’s animations aren’t affecting my PC at all. I have a quite respectable Ryzen 3900X and RTX 3070 in my rig, so I have no clue why CSS animations can cause it to crash. Asked a dozen times on various sites, but nobody knows why except «It’s a Chrome thing».. shrug
If you do not really need Chrome, it may be better for you to switch to another browser, such as Firefox.
Allow me to answer my own question. Setting animation-duration to 0s !important seems to be working. However, I added animation-play-state: paused for good measure as well.
I made an userscript, and found that it doesn’t target the Shadow DOM, so I have to traverse through every element, check if it’s a shadow root, and then add the CSS. Since elements can be added to a page dynamically, I decided to do this every second. So far I cannot see a performance difference, even on pages with a lot of elements.
Install TamperMonkey (Chrome) or GreaseMonkey (Firefox) to use this:
// ==UserScript== // @name Disable all animations // @version 1.0 // @author mortenmoulder // @include * // @grant GM_addStyle // @grant GM_addElement // ==/UserScript== //remove animations globally GM_addStyle("* < animation-duration: 0s !important; animation-play-state: paused; >"); var ignoreElements = []; //remove animations inside shadow DOM elements function findShadowRoots(elements) < for (var i = 0; i < elements.length; i++) < if(elements[i].shadowRoot) < if(ignoreElements.includes(elements[i].shadowRoot) == false) < GM_addElement(elements[i].shadowRoot, 'style', < textContent: '* < animation-duration: 0s !important; animation-play-state: paused;' >); ignoreElements.push(elements[i].shadowRoot); > findShadowRoots(elements[i].shadowRoot.querySelectorAll("*")); > > > //remove animations every 1 second setInterval(() => < var allNodes = document.querySelectorAll('*'); findShadowRoots(allNodes); >, 1000);