How I Used the WAAPI to Build an Animation Library

Avatar of Okiki Ojo
Okiki Ojo on (Updated on )

Take your JavaScript to the next level at Frontend Masters.

The Web Animations API lets us construct animations and control their playback with JavaScript. The API opens the browser’s animation engine to developers and was designed to underlie implementations of both CSS animations and transitions, leaving the door open to future animation effects. It is one of the most performant ways to animate on the Web, letting the browser make its own internal optimizations without hacks, coercion, or window.requestAnimationFrame().

With the Web Animations API, we can move interactive animations from stylesheets to JavaScript, separating presentation from behavior. We no longer need to rely on DOM-heavy techniques such as writing CSS properties and scoping classes onto elements to control playback direction. And unlike pure, declarative CSS, JavaScript also lets us dynamically set values from properties to durations. For building custom animation libraries and creating interactive animations, the Web Animations API might be the perfect tool for the job. Let’s see what it can do!

For the rest of this article, I will sometimes refer to the Web Animation API as WAAPI. When searching for resources on the Web Animation API, you might be led astray by searching “Web Animation API” so, to make it easy to find resources, I feel we should adopt the term WAAPI; tell me what you think in the comments below.

This is the library I made with the WAAPI

@okikio/animate is an animation library for the modern web. It was inspired by animateplus, and animejs; it is focused on performance and developer experience, and utilizes the Web Animation API to deliver butter-smooth animations at a small size, weighing in at ~5.79 KB (minified and gzipped).

The story behind @okikio/animate

In 2020, I decided to make a more efficient PJAX library, similar to Rezo Zero’sStarting Blocks project, but with the ease of use of barbajs. I felt starting blocks was easier to extend with custom functionality, and could be made smoother, faster, and easier to use.

Note: if you don’t know what a PJAX library is I suggest checking out MoOx/pjax; in short, PJAX allows for smooth transitions between pages using fetch requests and switching out DOM Elements.

Over time my intent shifted, and I started noticing how often sites from awwwards.com used PJAX, but often butchered the natural experience of the site and browser . Many of the sites looked cool at first glance, but the actual usage often told a different story — scrollbars were often overridden, prefetching was often too eager, and a lack of preparation for people without powerful internet connections, CPUs and/or GPUs. So, I decided to progressively enhance the library I was going to build. I started what I call the “native initiative” stored in the GitHub repo okikio/native; a means of introducing all the cool and modern features in a highly performant, compliant, and lightweight way.

For the native initiative I designed the PJAX library @okikio/native; while testing on an actual project, I ran into the Web Animation API, and realized there were no libraries that took advantage of it, so, I developed @okikio/animate, to create a browser compliant animation library. (Note: this was in 2020, around the same time use-web-animations by wellyshen was being developed. If you are using react and need some quick animate.css like effects, use-web-animations is a good fit.) At first, it was supposed to be simple wrapper but, little by little, I built on it and it’s now at 80% feature parity with more mature animation libraries.

Note: you can read more on the native initiative as well as the @okikio/native library on the Github repo okikio/native. Also, okikio/native, is a monorepo with @okikio/native and @okikio/animate being sub-packages within it.

Where @okikio/animate fits into this article

The Web Animation API is very open in design. It is functional on its own but it’s not the most developer-friendly or intuitive API, so I developed @okikio/animate to act as a wrapper around the WAAPI and introduce the features you know and love from other more mature animation libraries (with some new features included) to the high-performance nature of the Web Animation API. Give the project’s README a read for much more context.

Now, let’s get started

@okikio/animate creates animations by creating new instances of Animate (a class that acts as a wrapper around the Web Animation API).

import { Animate } from"@okikio/animate";

new Animate({
  target: [/* ... */],
  duration: 2000,
  // ... 
});

The Animate class receives a set of targets to animate, it then creates a list of WAAPI Animation instances, alongside a main animation (the main animation is a small Animation instance that is set to animate over a non-visible element, it exists as a way of tracking the progress of the animations of the various target elements), the Animate class then plays each target elements Animation instance, including the main animation, to create smooth animations.

The main animation is there to ensure accuracy in different browser vendor implementations of WAAPI. The main animation is stored in Animate.prototype.mainAnimation, while the target element’s Animation instances are stored in a WeakMap, with the key being its KeyframeEffect. You can access the animation for a specific target using the Animate.prototype.getAnimation(el).

You don‘t need to fully understand the prior sentences, but they will aid your understanding of what @okikio/animate does. If you want to learn more about how WAAPI works, check out MDN, or if you would like to learn more about the @okikio/animate library, I’d suggest checking out the okikio/native project on GitHub.

Usage, examples and demos

By default, creating a new instance of Animate is very annoying, so, I created the animate function, which creates new Animate instances every time it’s called.

import animate from "@okikio/animate";
// or
import { animate } from "@okikio/animate";

animate({ 
  target: [/* ... */],
  duration: 2000,
  // ... 
});

When using the @okikio/animate library to create animations you can do this:

import animate from "@okikio/animate";

// Do this if you installed it via the script tag: const { animate } = window.animate;

(async () => {
  let [options] = await animate({
    target: ".div",

    // Units are added automatically for transform CSS properties
    translateX: [0, 300],
    duration: 2000, // In milliseconds
    speed: 2,
  });

  console.log("The Animation is done...");
})();

You can also play with a demo with playback controls:

Try out Motion Path:

Try different types of Motion by changing the Animation Options:

I also created a complex demo page with polyfills:

You can find the source code for this demo in the animate.ts and animate.pug files in the GitHub repo. And, yes, the demo uses Pug, and is a fairly complex setup. I highly suggest looking at the README as a primer for getting started.

The native initiative uses Gitpod, so if you want to play with the demo, I recommend clicking the “Open in Gitpod” link since the entire environment is already set up for you — there’s nothing to configure.

You can also check out some more examples in this CodePen collection I put together. For the most part, you can port your code from animejs to @okikio/animate with few-to-no issues.

I should probably mention that @okikio/animate supports both the target and targets keywords for settings animation targets. @okikio/animate will merge both list of targets into one list and use Sets to remove any repeated targets. @okikio/animate supports functions as animation options, so you can use staggering similar to animejs. (Note: the order of arguments are different, read more in the “Animation Options & CSS Properties as Methods” section of the README file.)

Restrictions and limitations

@okikio/animate isn’t perfect; nothing really is, and seeing as the Web Animation API is a living standard constantly being improved, @okikio/animate itself still has lots of space to grow. That said, I am constantly trying to improve it and would love your input so please open a new issue, create a pull request or we can have a discussion over at the GitHub project.

The first limitation is that it doesn’t really have a built-in timeline. There are a few reasons for this:

  1. I ran out of time. I am still only a student and don’t have lots of time to develop all the projects I want to.
  2. I didn’t think a formal timeline was needed, as async/await programming was supported. Also, I added timelineOffset as an animation option, should anyone ever need to create something similar to the timeline in animejs.
  3. I wanted to make @okikio/animate as small as possible.
  4. With group effects and sequence effects coming soon, I thought it would be best to leave the package small until an actual need comes up. On that note, I highly suggest reading Daniel C. Wilson’s series on the WAAPI, particularly the fourth installment that covers group effects and sequence effects.

Another limitation of @okikio/animate is that it lacks support for custom easings, like spring, elastic, etc. But check out Jake Archibald’s proposal for an easing worklet. He discusses multiple standards that are currently in discussion. I prefer his proposal, as it’s the easiest to implement, not to mention the most elegant of the bunch. In the meanwhile, I’m taking inspiration from Kirill Vasiltsov article on Spring animations with WAAPI and I am planning to build something similar into the library.

The last limitation is that @okikio/animate only supports automatic units on transform functions e.g. translateX, translate, scale, skew, etc. This is no longer the case as of @okikio/[email protected], but there are still some limitations on CSS properties that support color. Check the GitHub release for more detail.

For example:

animate({
  targets: [".div", document.querySelectorAll(".el")],

  // By default "px", will be applied
  translateX: 300,
  left: 500,
  margin: "56 70 8em 70%",

  // "deg" will be applied to rotate instead of px
  rotate: 120, 

  // No units will be auto applied
  color: "rgb(25, 25, 25)",
  "text-shadow": "25px 5px 15px rgb(25, 25, 25)"
});

Looking to the future

Some future features, like ScrollTimeline, are right around the corner. I don’t think anyone actually knows when it will release but since the ScrollTimeline in Chrome Canary 92, I think it’s safe to say the chances of a release in the near future look pretty good.

I built the timeline animation option into @okikio/animate to future-proof it. Here’s an example:

Thanks to Bramus for the demo inspiration! Also, you may need the Canary version of Chrome or need to turn on Experimental Web Platform features in Chrome Flags to view this demo. It seems to work just fine on Firefox, though, so… 🤣.

If you want to read more on the ScrollTimeline, Bramus wrote an excellent article on it. I would also suggest reading the Google Developers article on Animation Worklets.

My hope is to make the library smaller. It’s currently ~5.79 KB which seems high, at least to me. Normally, I would use a bundlephobia embed but that has trouble bundling the project, so if you want to verify the size, I suggest using bundle.js.org because it actually bundles the code locally on your browser. I specifically built it for checking the bundle size of @okikio/animate, but note it’s not as accurate as bundlephobia.

Polyfills

One of the earlier demos shows polyfills in action. You are going to need web-animations-next.min.js from web-animations-js to support timelines. Other modern features the KeyframeEffect constructor is required.

The polyfill uses JavaScript to test if the KeyframeEffect is supported and, if it isn’t, the polyfill loads and does its thing. Just avoid adding async/defer to the polyfill, or it will not work the way you expect. You’ll also want to polyfill Map, Set, and Promise.

<html>
  <head>
    <!-- Async -->
    <script src="https://cdn.polyfill.io/v3/polyfill.min.js?features=default,es2015,es2018,Array.prototype.includes,Map,Set,Promise" async></script>
    <!-- NO Async/Defer -->
    <script src="./js/webanimation-polyfill.min.js"></script>
  </head>
  <body>
    <!-- Content -->
  </body>
</html>

And if you’re building for ES6+, I highly recommend using esbuild for transpiling, bundling, and minifying. For ES5, I suggest using esbuild (with minify off), Typescript (with target of ES5), and terser; as of now, this is the fastest setup to transpile to ES5, it’s faster and more reliable than babel. See the Gulpfile from the demo for more details.

Conclusion

@okikio/animate is a wrapper around the Web Animation API (WAAPI) that allows you to use all the features you love from animejs and other animation libraries, in a small and concise package. So, what are your thoughts after reading about it? Is it something you think you’ll reach for when you need to craft complex animations? Or, even more important, is there something that would hold you back from using it? Leave a comment below or join the discussion on Github Discussions.


This article originally appeared on dev.to, it also appeared on hackernoon.com and my blog blog.okikio.dev.
Photo by Pankaj Patel on Unsplash.