loading...
Cover image for CSS Houdini's Animation API & animation timeline explained

CSS Houdini's Animation API & animation timeline explained

adrianbdesigns profile image Adrian Bece Originally published at smashingmagazine.com ・5 min read

This post has been originally published on Smashing Magazine and I decided to split it into parts to make it more digestible. I'll be converting the post into markdown and publish a part of it on DEV every week. If you want to read it right away in its entirety, feel free to read it on Smashing Magazine until all parts are available on DEV. Thank you.


Animation API

The Animation API extends web animations with options to listen to various events (scroll, hover, click, etc.) and improves performance by running animations on their own dedicated thread using an Animation Worklet. It allows for user action to control the flow of animation that runs in a performant, non-blocking way.

Like any Worklet, Animation Worklet needs to be registered first.

registerAnimator("animationWorkletExample", class {
  constructor(options) {
    /* ... */
  }
  animate(currentTime, effect) {
    /* ... */
  }
});

This class consists of two functions:

  • constructor: called when a new instance is created. Used for general setup.
  • animate: the main function that contains the animation logic. Provides the following input arguments:
    • currentTime : the current time value from the defined timeline
    • effect: an array of effects that this animation uses

After the Animation Worklet has been registered, it needs to be included in the main JavaScript file, animation (element, keyframes, options) needs to be defined and animation is instantiated with the selected timeline. Timeline concepts and web animation basics will be explained in the next section.

/* Include Animation Worklet */
await CSS.animationWorklet.addModule("path/to/worklet/file.js");;

/* Select element that's going to be animated */
const elementExample = document.getElementById("elementExample");

/* Define animation (effect) */
const effectExample = new KeyframeEffect(
  elementExample,  /* Selected element that's going to be animated */
  [ /* ... */ ],   /* Animation keyframes */
  { /* ... */ },   /* Animation options - duration, delay, iterations, etc. */
);

/* Create new WorkletAnimation instance and run it */
new WorkletAnimation(
  "animationWorkletExample"  /* Worklet name */
  effectExample,             /* Animation (effect) timeline */
  document.timeline,         /* Input timeline */
  {},                        /* Options passed to constructor */
).play();                    /* Play animation */

Timeline Mapping

Web animation is based on timelines and mapping of the current time to a timeline of an effect’s local time. For example, let’s take a look at a repeating linear animation with 3 keyframes (start, middle, last) that runs 1 second after a page is loaded (delay) and with a 4-second duration.

Effect timeline from the example would look like this (with the 4-second duration with no delay):

Effect timeline (4s duration) Keyframe
0ms First keyframe - animation starts
2000ms Middle keyframe - animation in progress
4000ms Last keyframe - animation ends or resets to first keyframe

In order to better understand effect.localTime, by setting its value to 3000ms (taking into account 1000ms delay), resulting animation is going to be locked to a middle keyframe in effect timeline (1000ms delay + 2000ms for a middle keyframe). The same effect is going to happen by setting the value to 7000ms and 11000ms because the animation repeats in 4000ms interval (animation duration).

animate(currentTime, effect) {
  effect.localTime = 3000; // 1000ms delay + 2000ms middle keyframe
}

No animation happens when having a constant effect.localTime value because animation is locked in a specific keyframe. In order to properly animate an element, its effect.localTime needs to be dynamic. It’s required for the value to be a function that depends on the currentTime input argument or some other variable.

The following code shows a functional representation of 1:1 (linear function) mapping of a timeline to effect local time.

animate(currentTime, effect) {
  effect.localTime = currentTime; // y = x linear function
}
Timeline (document.timeline) Mapped effect local time Keyframe
startTime + 0ms (elapsed time) startTime + 0ms First
startTime + 1000ms (elapsed time) startTime + 1000ms (delay) + 0ms First
startTime + 3000ms (elapsed time) startTime + 1000ms (delay) + 2000ms Middle
startTime + 5000ms (elapsed time) startTime + 1000ms (delay) + 4000ms Last / First
startTime + 7000ms (elapsed time) startTime + 1000ms (delay) + 6000ms Middle
startTime + 9000ms (elapsed time) startTime + 1000ms (delay) + 8000ms Last / First

Timeline isn’t restricted to 1:1 mapping to effect’s local time. Animation API allows developers to manipulate the timeline mapping in animate function by using standard JavaScript functions to create complex timelines. Animation also doesn’t have to behave the same in each iteration (if animation is repeated).

Animation doesn’t have to depend on the document’s timeline which only starts counting milliseconds from the moment it’s loaded. User actions like scroll events can be used as a timeline for animation by using a ScrollTimeline object. For example, an animation can start when a user has scrolled to 200 pixels and can end when a user has scrolled to 800 pixels on a screen.

const scrollTimelineExample = new ScrollTimeline({
  scrollSource: scrollElement,  /* DOM element whose scrolling action is being tracked */
  orientation: "vertical",      /* Scroll direction */
  startScrollOffset: "200px",   /* Beginning of the scroll timeline */
  endScrollOffset: "800px",    /* Ending of the scroll timeline */
  timeRange: 1200,              /* Time duration to be mapped to scroll values*/
  fill: "forwards"              /* Animation fill mode */
});

The animation will automatically adapt to user scroll speed and remain smooth and responsive. Since Animation Worklets are running off the main thread and are connected to a browser’s rending engine, animation that depends on user scroll can run smoothly and be very performant.

Example

The following example showcases how a non-linear timeline implementation. It uses modified Gaussian function and applies translation and rotation animation with the same timeline. Complete source code is available on the example repository.

Animation created with Animation API which is using modified Gaussian function time mapping

Animation created with Animation API which is using modified Gaussian function time mapping

Feature Detection

if (CSS.animationWorklet) {
  /* ... */
}

W3C Specification Status

First Public Working Draft: ready for community review, prone to specification change

Browser Support

Google Chrome Microsoft Edge Opera Browser Firefox Safari
Partial support (*) Partial support (*) Partial support (*) Not supported Not supported


(*) supported with “Experimental Web Platform features” flag enabled.


Data source: Is Houdini Ready Yet?


These articles are fueled by coffee. So if you enjoy my work and found it useful, consider buying me a coffee! I would really appreciate it.

Buy Me A Coffee

Thank you for taking the time to read this post. Keep an eye out for the next part in the series. If you've found this useful, please give it a ❤️ or 🦄, share and comment.

Posted on by:

adrianbdesigns profile

Adrian Bece

@adrianbdesigns

React, Frontend, Magento 2 certified developer. Magento PWA Studio contributor. Rock and metal music fan. Reads Dune, sci-fi novels and Calvin & Hobbes. Creates amazing interfaces @ prototyp.digital

Discussion

markdown guide