CSS Houdini's Animation API & animation timeline explained
Tags: javascript, webdev, tutorial, css
Published: 2020-07-15 05:23:38

<small>This post has been [originally published on Smashing Magazine](https://www.smashingmagazine.com/2020/03/practical-overview-css-houdini/) 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.</small>

<hr/>

## 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.

```js
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.

```js
/* 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):
<table>
<thead><tr><th>Effect timeline (4s duration)</th><th>Keyframe</th></tr></thead><tbody><tr><td>0ms</td><td>First keyframe - animation starts</td></tr><tr><td>2000ms</td><td>Middle keyframe - animation in progress</td></tr><tr><td>4000ms</td><td>Last keyframe - animation ends or resets to first keyframe</td></tr></tbody>
</table>

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).

```js
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.

```js
animate(currentTime, effect) {
  effect.localTime = currentTime; // y = x linear function
}
```
<table>
<thead><tr><th>Timeline (<code>document.timeline</code>)</th><th>Mapped effect local time</th><th>Keyframe</th></tr></thead><tbody><tr><td><code>startTime</code> + 0ms (elapsed time)</td><td><code>startTime</code> + 0ms</td><td>First</td></tr><tr><td><code>startTime</code> + 1000ms (elapsed time)</td><td><code>startTime</code> + 1000ms (delay) + 0ms</td><td>First</td></tr><tr><td><code>startTime</code> + 3000ms (elapsed time)</td><td><code>startTime</code> + 1000ms (delay) + 2000ms</td><td>Middle</td></tr><tr><td><code>startTime</code> + 5000ms (elapsed time)</td><td><code>startTime</code> + 1000ms (delay) + 4000ms</td><td>Last / First</td></tr><tr><td><code>startTime</code> + 7000ms (elapsed time)</td><td><code>startTime</code> + 1000ms (delay) + 6000ms</td><td>Middle</td></tr><tr><td><code>startTime</code> + 9000ms (elapsed time)</td><td><code>startTime</code> + 1000ms (delay) + 8000ms</td><td>Last / First</td></tr></tbody></table>

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.

```js
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](https://mathworld.wolfram.com/GaussianFunction.html) and applies translation and rotation animation with the same timeline. Complete source code is available on the [example repository](https://github.com/codeAdrian/houdini-examples/tree/master/animation-api-example).

<figure>
![Animation created with Animation API which is using modified Gaussian function time mapping](https://dev-to-uploads.s3.amazonaws.com/i/uj6h19e62htr8jbm710g.gif)
<figcaption>Animation created with Animation API which is using modified Gaussian function time mapping</figcaption>
</figure>

### Feature Detection
```js
if (CSS.animationWorklet) {
  /* ... */
}
```

### W3C Specification Status

[First Public Working Draft](https://drafts.css-houdini.org/css-animationworklet/): ready for community review, prone to specification change

### Browser Support

<table>
<thead><tr><th>Google Chrome</th><th>Microsoft Edge</th><th>Opera Browser</th><th>Firefox</th><th>Safari</th></tr></thead><tbody><tr><td>Partial support (*)</td><td>Partial support (*)</td><td>Partial support (*)</td><td>Not supported</td><td>Not supported</td></tr></tbody>
</table>

<small>
(*) supported with “Experimental Web Platform features” flag enabled.
</small>
<small>
Data source: [Is Houdini Ready Yet?](https://ishoudinireadyyet.com/)
</small>

<hr/>
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.

<a href="https://www.buymeacoffee.com/ubnZ8GgDJ" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-red.png" alt="Buy Me A Coffee" style="height: 51px !important;width: 217px !important;" ></a>

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.