Categories
css javascript

Restart animation in CSS3: any better way than removing the element?

92

I have a CSS3 animation that needs to be restarted on a click. It’s a bar showing how much time is left. I’m using the scaleY(0) transform to create the effect.

Now I need to restart the animation by restoring the bar to scaleY(1) and let it go to scaleY(0) again.
My first attempt to set scaleY(1) failed because it takes the same 15 seconds to bring it back to full length. Even if I change the duration to 0.1 second, I would need to delay or chain the assignment of scaleY(0) to let the bar replenishment complete.
It feels too complicated for such a simple task.

I also found an interesting tip to restart the animation by removing the element from the document, and then re-inserting a clone of it:
http://css-tricks.com/restart-css-animation/

It works, but is there a better way to restart a CSS animation?
I’m using Prototype and Move.js, but I’m not restricted to them.

4

  • 1

    possible duplicate of How do I re-trigger a WebKit CSS animation via JavaScript?

    – Bergi

    Jun 25, 2013 at 17:30

  • You can read in the updated blog post an other technique forcing to reflow the element: element.offsetWidth = element.offsetWidth;

    – mems

    Oct 29, 2013 at 17:11

  • I found cloning was the best solution, as per your CSS-Tricks link.

    – Dunc

    Feb 28, 2020 at 17:01


  • 2

    TL;DR: e.style.animation = 'none'; e.offsetHeight; e.style.animation = ...; Or, if you’re using classes: e.classList.remove('a'); e.offsetHeight; e.classList.add('a');

    – Andrew

    Oct 1, 2020 at 21:01

87

Just set the animation property via JavaScript to "none" and then set a timeout that changes the property to "", so it inherits from the CSS again.

Demo for Webkit here: http://jsfiddle.net/leaverou/xK6sa/
However, keep in mind that in real world usage, you should also include -moz- (at least).

6

  • Thanks Lea. Almost there :), If I change your animation to run only once I don’t quite get the same effect. When I click, the animation doesn’t start over again.

    – Leo

    Jun 11, 2011 at 11:09


  • Thanks a lot! – But unfortunately i cannot get it to work in Safari. Chrome, Edge and Firefox are working as expected. i use following code: var anim = jQuery(mutation.target).find(".background.background-image:first").get(0); anim.style.WebkitAnimation = 'none'; anim.style.animation = 'none'; setTimeout(function() { anim.style.WebkitAnimation = ''; anim.style.animation = ''; }, 10); }

    – dheil

    Feb 24, 2017 at 14:45


  • 1

    Not good enough, because sometimes a flaw could be seen if timeout is too long, and the effect isn’t taken if timeout is too short. Not recommended if you need to restart animation when it is still playing.

    – Eric

    Apr 2, 2018 at 13:25

  • 6

    It’s 2019 now, vendor prefix for this should no longer be necessary.

    Jan 28, 2019 at 2:12

  • 3

    To avoid the timeout problems described by @Eric, you can call void element.offsetWidth; to force a reflow in between the animation property changes instead of using a timeout.

    – John Qian

    Oct 9, 2019 at 8:08

87

Just set the animation property via JavaScript to "none" and then set a timeout that changes the property to "", so it inherits from the CSS again.

Demo for Webkit here: http://jsfiddle.net/leaverou/xK6sa/
However, keep in mind that in real world usage, you should also include -moz- (at least).

6

  • Thanks Lea. Almost there :), If I change your animation to run only once I don’t quite get the same effect. When I click, the animation doesn’t start over again.

    – Leo

    Jun 11, 2011 at 11:09


  • Thanks a lot! – But unfortunately i cannot get it to work in Safari. Chrome, Edge and Firefox are working as expected. i use following code: var anim = jQuery(mutation.target).find(".background.background-image:first").get(0); anim.style.WebkitAnimation = 'none'; anim.style.animation = 'none'; setTimeout(function() { anim.style.WebkitAnimation = ''; anim.style.animation = ''; }, 10); }

    – dheil

    Feb 24, 2017 at 14:45


  • 1

    Not good enough, because sometimes a flaw could be seen if timeout is too long, and the effect isn’t taken if timeout is too short. Not recommended if you need to restart animation when it is still playing.

    – Eric

    Apr 2, 2018 at 13:25

  • 6

    It’s 2019 now, vendor prefix for this should no longer be necessary.

    Jan 28, 2019 at 2:12

  • 3

    To avoid the timeout problems described by @Eric, you can call void element.offsetWidth; to force a reflow in between the animation property changes instead of using a timeout.

    – John Qian

    Oct 9, 2019 at 8:08

16

@ZachB’s answer about the Web Animation API seems like “right”™ way to do this, but unfortunately seems to require that you define your animations through JavaScript. However it caught my eye and I found something related that’s useful:

Element.getAnimations() and Document.getAnimations()

The support for them is pretty good as of 2021.

In my case, I wanted to restart all the animations on the page at the same time, so all I had to do was this:

const replayAnimations = () => {
  document.getAnimations().forEach((anim) => {
    anim.cancel();
    anim.play();
  });
};

But in most cases people will probably want to select which animation they restart…

getAnimations returns a bunch of CSSAnimation and CSSTransition objects that look like this:

animationName: "fade"
currentTime: 1500
effect: KeyframeEffect
  composite: "replace"
  pseudoElement: null
  target: path.line.yellow
finished: Promise {<fulfilled>: CSSAnimation}
playState: "finished"
ready: Promise {<fulfilled>: CSSAnimation}
replaceState: "active"
timeline: DocumentTimeline {currentTime: 135640.502}

# ...etc

So you could use the animationName and target properties to select just the animations you want (albeit a little circuitously).


EDIT

Here’s a handy function that might be more compatible using just Document.getAnimations, with TypeScript thrown in for demonstration/fun:

// restart animations on a given dom element
const restartAnimations = (element: Element): void => {
  for (const animation of document.getAnimations()) {
    if (element.contains((animation.effect as KeyframeEffect).target)) {
      animation.cancel();
      animation.play();
    }
  }
};

2

  • If only this was supported across all browsers, Safari always late to the show..

    Aug 12, 2021 at 19:25

  • Note that the support for Element.getAnimations() and Document.getAnimations() is different. The latter seems to be supported by Safari, and everything but IE. So to be more reliable, use the latter, and you’ll just have to do some extra manual filtering.

    Aug 13, 2021 at 16:10