Home > Blog > Web Animations Finished

Web Animations problem - not every browser implements finished

Recently, I come across an interesting fact about Web Animations. Before deep diving into the problem, for those who is not familiar with such web technology, here's the basic definition from MDN:

The Web Animations API allows for synchronizing and timing changes to the presentation of a Web page, i.e. animation of DOM elements. It does so by combining two models: the Timing Model and the Animation Model.

CSS Animations

In essence, how we web devs animate virtually anything on the web is by CSS aka CSS Animations. However, in some of today's real world use cases, it does not fit really well. This is how Web Animations actually shines. We can control how we run the animation using JavaScript on the web. We can either control the speed of the animation, reverse the animation, or even schedule something more important stuff right after an animation easily. It's dope, isn't it. In fact, it is only when a browser supports such piece of cool tech natively in order to achieve the optimal performance with more granular controls.

The missing finished property

Today's topic is about one of the quite common problems even for a really simple use case, that is, the missing finished property. This may not suprise you because Web Animations can be Promise based even though it comes with a full suite of event handlers such as oncancel, onfinish, and what not. Using Promises on the web is neat especially when you need to schedule a series of tasks to be executed at your will either sequentially or in parallel. However, as of today, only Mozilla Firefox 63+ fully implements Web Animations. Google Chrome has it implemented for ages but it is yet to be a complete one. This causes a great confusion for all the Web Animations lovers as feature detection can be a little bit tricky when come to Web Animations.

Feature detect Web Animations

If you are relying on the Promise based API, this is what you'd normally write:

const hasElementAnimate = 'animate' in Element.prototype;
const hasFinished = hasElementAnimate && document.createElement('div').animate([]).finished != null;

if (hasElementAnimate && hasFinished) {
  const el = document.body.querySelector('.animatable');
  const anim = el.animate([
    { transform: 'translate3d(0, 0, 0)' },
    { transform: 'translate3d(100px, 0, 0)' },
  ]);

  anim.finished.then(() => {
    /** Do more stuff when animation completes */
  });
} else {
  /** Fallback */
}

Dealing with the missing finished property

This shows an obvious signal that you can in fact achieve the same behavior pretty easily without much code and this is my take on that by leveraging one of the event handlers onfinish:

/** Assuming `Web Animations` is supported partially... */
const el = document.body.querySelector('.animatable');
const anim = el.animate([
  { transform: 'translate3d(0, 0, 0)', opacity: '0' },
  { transform: 'translate3d(100px, 0, 0)', opacity: '1' },
]);
/**
 * By passing the `resolve` callback of a `Promise` as the function to the `onfinish` handler,
 * the `Promise` fulfills when the animation completes. Technically this is somewhat `finished`.
 * Do you agree? 😄
 */
const animTask = new Promise(yay => (anim.onfinish = yay));

animTask.then(() => {
  /** Schedule more stuff when animation completes */
});

Wrap up

I came up with such technique which is much more straightforward than any polyfills/ solutions I've found so far on the Internet. It has been proven to work without breaking stuff for my use cases and your mileage might vary (disclaimer!). Maybe someone could have come up with such succint solution somewhere on the planet, who knows. Just another little snippet here to share to my readers who encounter the same problem and hope that this simple trick helps making your life easier.

That is it. Have a wonderful day ahead and I'll see you in the upcoming blog post. Peace! ✌️