✨ React hook for highly-performant and manipulable animations using Web Animations API
Tags: react, hook, opensource, javascript
Published: 2020-07-02 07:40:35

Using [Web Animations API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API) (a.k.a WAAPI) in the React [hook](https://reactjs.org/docs/hooks-custom.html#using-a-custom-hook) way. Let's create highly-performant, flexible and manipulable web animations in the modern world. Hope you guys 👍🏻 it!

![demo_basic](https://user-images.githubusercontent.com/21308003/91186058-280b7d00-e721-11ea-8180-e739d14c7ec7.gif)

⚡️ Try yourself: https://use-web-animations.netlify.app

![demo_animate](https://user-images.githubusercontent.com/21308003/91186066-2a6dd700-e721-11ea-84c5-b2c4ff984cf5.gif)

⚡️ Try yourself: https://use-web-animations.netlify.app#animations

## Features

- 🚀 Animate on the Web with highly-performant and manipulable way, using [Web Animations API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API).
- 🎣 Easy to use, based on React [hook](https://reactjs.org/docs/hooks-custom.html#using-a-custom-hook).
- 🎛 Super flexible [API](https://github.com/wellyshen/use-web-animations#api) design that can cover [all the cases](#usage) that you need.
- 🎞 [Built-ins animations](#use-builtin-animations) for you, based on [Animate.css](https://animate.style).
- 🔩 Supports custom `refs` for [some reasons](https://github.com/wellyshen/use-web-animations#use-your-own-ref).
- 📜 Supports [TypeScript](https://www.typescriptlang.org) type definition.
- 🗄️ Server-side rendering compatibility.
- 🦔 Tiny size ([~ 4.4KB gzipped](https://bundlephobia.com/result?p=@wellyshen/use-web-animations)). No external dependencies, aside for the `react`.

## Usage

The [API](https://github.com/wellyshen/use-web-animations#api) design of the hook not only inherits the DX of the [Web Animations API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API) but also provides useful features and sugar events to us. Here are some examples to show you how does it work.

### Basic Usage

Create an animation by the `keyframes` and `animationOptions` options (these are the [parameters](https://developer.mozilla.org/en-US/docs/Web/API/Element/animate#parameters) of the `Element.animate()`).

[![Edit useWebAnimations - basic](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/usewebanimations-basic-nf0kd?fontsize=14&hidenavigation=1&theme=dark)

```js
import useWebAnimations from "@wellyshen/use-web-animations";

const App = () => {
  const { ref, playState } = useWebAnimations({
    keyframes: {
      transform: ["translateX(500px)"], // Move by 500px
      background: ["red", "blue", "green"], // Go through three colors
    },
    animationOptions: {
      delay: 500, // Start with a 500ms delay
      duration: 1000, // Run for 1000ms
      iterations: 2, // Repeat once
      direction: "alternate", // Run the animation forwards and then backwards
      easing: "ease-in-out", // Use a fancy timing function
    },
    onReady: ({ playState, animate, animation }) => {
      // Triggered when the animation is ready to play (Google Chrome: available in v84+)
    },
    onUpdate: ({ playState, animate, animation }) => {
      // Triggered when the animation enters the running state or changes state
    },
    onFinish: ({ playState, animate, animation }) => {
      // Triggered when the animation enters the finished state (Google Chrome: available in v84+)
    },
    // More useful options...
  });

  return (
    <div className="container">
      <p>🍿 Animation is {playState}</p>
      <div className="target" ref={ref} />
    </div>
  );
};
```

### Playback Control

The shortcoming with existing technologies was the lack of playback control. The Web Animations API provides several useful methods for controlling playback: play, pause, reverse, cancel, finish, seek, control speed via the [methods](https://developer.mozilla.org/en-US/docs/Web/API/Animation#Methods) of the **Animation** interface. This hook exposes the animation instance for us to interact with animations, we can access it by the `getAnimation()` return value.

[![Edit useWebAnimations - controls](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/usewebanimations-controls-hst8v?fontsize=14&hidenavigation=1&theme=dark)

```js
import useWebAnimations from "@wellyshen/use-web-animations";

const App = () => {
  const { ref, playState, getAnimation } = useWebAnimations({
    playbackRate: 0.5, // Change playback rate, default is 1
    autoPlay: false, // Automatically starts the animation, default is true
    keyframes: { transform: ["translateX(500px)"] },
    animationOptions: { duration: 1000, fill: "forwards" },
  });

  const play = () => {
    getAnimation().play();
  };

  const pause = () => {
    getAnimation().pause();
  };

  const reverse = () => {
    getAnimation().reverse();
  };

  const cancel = () => {
    getAnimation().cancel();
  };

  const finish = () => {
    getAnimation().finish();
  };

  const seek = (e) => {
    const animation = getAnimation();
    const time = (animation.effect.getTiming().duration / 100) * e.target.value;
    animation.currentTime = time;
  };

  const updatePlaybackRate = (e) => {
    getAnimation().updatePlaybackRate(e.target.value);
  };

  return (
    <div className="container">
      <button onClick={play}>Play</button>
      <button onClick={pause}>Pause</button>
      <button onClick={reverse}>Reverse</button>
      <button onClick={cancel}>Cancel</button>
      <button onClick={finish}>Finish</button>
      <input type="range" onChange={seek} />
      <input type="number" defaultValue="1" onChange={updatePlaybackRate} />
      <div className="target" ref={ref} />
    </div>
  );
};
```

### Getting Animation's Information

When using the Web Animations API, we can get the information of an animation via the [properties](https://developer.mozilla.org/en-US/docs/Web/API/Animation#Properties) of the **Animation** interface. However, we can get the information of an animation by the `getAnimation()` return value as well.

```js
import useWebAnimations from "@wellyshen/use-web-animations";

const App = () => {
  const { ref, getAnimation } = useWebAnimations({
    keyframes: { transform: ["translateX(500px)"] },
    animationOptions: { duration: 1000, fill: "forwards" },
  });

  const speedUp = () => {
    const animation = getAnimation();
    animation.updatePlaybackRate(animation.playbackRate * 0.25);
  };

  const jumpToHalf = () => {
    const animation = getAnimation();
    animation.currentTime = animation.effect.getTiming().duration / 2;
  };

  return (
    <div className="container">
      <button onClick={speedUp}>Speed Up</button>
      <button onClick={jumpToHalf}>Jump to Half</button>
      <div className="target" ref={ref} />
    </div>
  );
};
```

The animation instance isn't a part of [React state](https://reactjs.org/docs/hooks-state.html), which means we need to access it by the `getAnimation()` whenever we need. If you want to monitor an animation's information, here's the `onUpdate` event for you. The event is implemented by the [requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) internally and the event callback is triggered when the `animation.playState` is running or changes.

```js
import { useState } from "react";
import useWebAnimations from "@wellyshen/use-web-animations";

const App = () => {
  const [showEl, setShowEl] = useState(false);
  const { ref } = useWebAnimations({
    keyframes: { transform: ["translateX(500px)"] },
    animationOptions: { duration: 1000, fill: "forwards" },
    onUpdate: ({ animation }) => {
      if (animation.currentTime > animation.effect.getTiming().duration / 2)
        setShowEl(true);
    },
  });

  return (
    <div className="container">
      {showEl && <div className="some-element" />}
      <div className="target" ref={ref} />
    </div>
  );
};
```

### Dynamic Interactions with Animation

We can create and play an animation at the `animationOptions` we want by the `animate` method, which is implemented based on the [Element.animate()](https://developer.mozilla.org/en-US/docs/Web/API/Element/animate). It's useful for interactions and the [composite modes](https://css-tricks.com/additive-animation-web-animations-api).

Let's create a mouse interaction effect:

[![Edit useWebAnimations - interaction](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/usewebanimations-interaction-4jrs9?fontsize=14&hidenavigation=1&theme=dark)

```js
import { useEffect } from "react";
import useWebAnimations from "@wellyshen/use-web-animations";

const App = () => {
  const { ref, animate } = useWebAnimations();

  useEffect(() => {
    document.addEventListener("mousemove", (e) => {
      // The target will follow the mouse cursor
      animate({
        keyframes: { transform: `translate(${e.clientX}px, ${e.clientY}px)` },
        animationOptions: { duration: 500, fill: "forwards" },
      });
    });
  }, [animate]);

  return (
    <div className="container">
      <div className="target" ref={ref} />
    </div>
  );
};
```

Create a bounce effect via lifecycle and composite mode:

```js
import useWebAnimations from "@wellyshen/use-web-animations";

const App = () => {
  const { ref, animate } = useWebAnimations({
    id: "fall", // Set animation id, default is empty string
    keyframes: [{ top: 0, easing: "ease-in" }, { top: "500px" }],
    animationOptions: { duration: 300, fill: "forwards" },
    onFinish: ({ animate, animation }) => {
      // Lifecycle is triggered by each animation, we can check the id to prevent animation from repeating
      if (animation.id === "bounce") return;

      animate({
        id: "bounce",
        keyframes: [
          { top: "500px", easing: "ease-in" },
          { top: "10px", easing: "ease-out" },
        ],
        animationOptions: { duration: 300, composite: "add" },
      });
    },
  });

  return (
    <div className="container">
      <div className="target" ref={ref} />
    </div>
  );
};
```

> ⚠️ Composite modes isn't fully supported by all the browsers, please check the [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/API/KeyframeEffect/composite#Browser_compatibility) carefully before using it.

## Use Built-in Animations

Too lazy to think about animation? We provide a collection of ready-to-use animations for you, they are implemented based on [Animate.css](https://animate.style).

👉🏻 [Check out the demo](https://use-web-animations.netlify.app#animations).

```js
import useWebAnimations, { bounce } from "@wellyshen/use-web-animations";

const App = () => {
  // Add a pre-defined effect to the target
  const { ref } = useWebAnimations({ ...bounce });

  return (
    <div className="container">
      <div className="target" ref={ref} />
    </div>
  );
};
```

We can customize the built-in animation by overriding its properties:

```js
const { keyframes, animationOptions } = bounce;
const { ref } = useWebAnimations({
  keyframes,
  animationOptions: {
    ...animationOptions,
    delay: 1000, // Delay 1s
    duration: animationOptions.duration * 0.75, // Speed up the animation
  },
});
```

[See all available animations](https://use-web-animations.netlify.app#animations)

---

Thanks for reading, for more usage details check out the project's GitHub page: https://github.com/wellyshen/use-web-animations

You can also install this package is distributed via [npm](https://www.npmjs.com/package/@wellyshen/use-web-animations).

```sh
$ yarn add @wellyshen/use-web-animations
# or
$ npm install --save @wellyshen/use-web-animations
```