Introducing react-tweenful: Animation engine for React
Tags: react, opensource, animation, javascript
Published: 2020-03-11 18:45:50

Hello, everyone,

I'm very glad to have released an opensource project I've been working on lately - `react-tweenful`. Hence I'm sharing this with you and looking for your feedback! 

## Repository

https://github.com/teodosii/react-tweenful

## Demo

https://teodosii.github.io/react-tweenful/

## What is react-tweenful?

`react-tweenful` it's an animation engine written from scratch to help you animate stuff easily. Initially I wanted a replacement for `react-transition-group` but with an animation engine behind, hence I've written the engine and then written individual components to handle multiple cases - `Tweenful`, `SVG`, `Observer` and `ObserverGroup`. It's inspired by `anime.js`.

## Features

* Loop support (infinite or up to a specific number)
* Wide variety of easings (bezier, predefined and custom easing)
* Delayed animations (before and after)
* Events support
* Negative delay support to mimic CSS animations
* Percent based animations to mimic CSS animations (e.g. `0%`, `50%`, `100%`)
* `Tweenful` component for animating DOM nodes
* `SVG` component to animate SVG nodes
* `Observer` component for mount/unmount animations
* `ObserverGroup` component to handle child transition (list removal/insertion, page transition etc)

## Usage

`react-tweenful` exports the following:

* `Tweenful` - component to animate DOM elements. It requires a DOM node to perform animation on.
* `SVG` - component to animate SVG elements. It requires a SVG node to perform animation on.
* `Observer` - component to animate mounting and unmounting of an element.
* `ObserverGroup` - component to watch over a list of `Observer` elements such as list removal/insertion or route transition

A couple of utility functions are also exported to help you out animating:

* `percentage` for percentage based animations
* `bezier` for bezier easings
* `elastic` for elastic easing

Import the needed component, for example `Tweenful`

```jsx
import Tweenful, { elastic } from 'react-tweenful';
```

`Tweenful` requires a node to render on which it will perform the animation. We've got most of the DOM nodes covered in the form of namespacing such as `Tweenful.div`, `Tweenful.span` and so on.

```jsx
const Example = () => (
  <Tweenful.div	
    className="tween-box"	
    duration={2000}	
    easing={elastic(1, 0.1)}
    style={{ position: 'relative' }}	
    animate={{ left: ['0px', '250px'] }}
  ></Tweenful.div>
);
```

## Real world examples

### Animate page route transition

`react-tweenful` can be used to animate children removal/insertion of a list or to animate betweeen routes with the help of `Observer` and `ObserverGroup`

![Notifications](https://github.com/teodosii/react-tweenful/raw/master/gif/notifications.gif "Notifications")

```jsx
<ObserverGroup
  config={{
    duration: 800,
    style: { overflow: 'hidden' },
    mount: { opacity: [0, 1], height: ['0px', 'auto'] },
    unmount: { opacity: [1, 0], height: ['auto', '0px'] },
    easing: 'easeInOutCubic'
  }}
  skipInitial={true}
>
  {this.state.notifications.map(notification => (
    <Notification
      key={notification.id}
      notification={notification}
      onClick={this.removeNotification}
    />
  ))}
</ObserverGroup>
```

Next example shows how easily we can animate when route changes. Watch the URL getting changed and see how the content is animated using a fade-in fade-out animation between previous and current page.

![Routing](https://github.com/teodosii/react-tweenful/raw/master/gif/transition.gif "Routing")


### Prism

This example shows usage of negative delay, which was implemented into the library to mimic CSS animations.

![Prism](https://github.com/teodosii/react-tweenful/raw/master/gif/rotating-svg.gif "Prism")


```jsx
import React from 'react';
import { SVG } from 'react-tweenful';

const WAVE_COUNT = 16;
const offset = 40;
const waveLength = 375;
const duration = 1500;

const waves = new Array(WAVE_COUNT).fill(0).map((wave, i) => ({
  key: i + 1,
  style: {
    transformOrigin: '500px 500px',
    opacity: 4 / WAVE_COUNT,
    mixBlendMode: 'screen',
    fill: `hsl(${(360 / WAVE_COUNT) * (i + 1)}, 100%, 50%)`,
    transform: `rotate(${(360 / WAVE_COUNT) * i}deg) translate(${waveLength}px, ${offset}px)`
  },
  rotate: `${(360 / WAVE_COUNT) * (i + 1)}deg`,
  translate: `${waveLength}px ${offset}px`,
  angle: `${(360 / WAVE_COUNT) * (i + 1)}deg`,
  delay: (duration / WAVE_COUNT) * (i + 1) * -3,
  path:
    'M-1000,1000V388c86-2,111-38,187-38s108,38,187,38,111-38,187-38,108,38,187,38,111-38,187-38,108,38,187,38,111-38,187-38,109,38,188,38,110-38,187-38,108,38,187,38,111-38,187-38,108,38,187,38,111-38,187-38,108,38,187,38,111-38,187-38,109,38,188,38c0,96,0,612,0,612Z'
}));

const RotatingSvg = () => {
  return (
    <svg height="300" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 1000 1000">
      <defs>
        <clipPath id="world">
          <circle cx="500" cy="500" r="450" stroke="none" fill="none"></circle>
        </clipPath>
      </defs>
      <circle cx="500" cy="500" r="450" stroke="none" fill="#000"></circle>
      <SVG
        type="g"
        className="circle"
        loop={true}
        duration={(WAVE_COUNT / 2) * duration}
        style={{ transformOrigin: '500px 500px' }}
        easing="linear"
        animate={{ rotate: '360deg' }}
      >
        {waves.map(wave => (
          <SVG.path
            loop={true}
            id={wave.key}
            key={wave.key}
            easing="linear"
            duration={1500}
            d={wave.path}
            style={wave.style}
            delay={wave.delay}
            transform={{ rotate: wave.rotate, translate: wave.translate }}
            animate={{ rotate: `${wave.angle}`, translate: `0px ${offset}px` }}
          />
        ))}
      </SVG>
    </svg>
  );
};
```

### SVG

The following shows usage of `SVG` component to animate path from start to end and then fill the form.

![SVG](https://github.com/teodosii/react-tweenful/raw/master/gif/svg-path.gif "SVG")

```jsx
const SvgDemo = () => {
  return (
    <svg
      height="300"
      xmlns="http://www.w3.org/2000/svg"
      x="0px"
      y="0px"
      viewBox="0 0 400 400"
    >
      <SVG.path
        duration={3000}
        easing="easeInQuad"
        style={{ fill: "#fff", transform: 'scale(4)' }}
        animate={[{ strokeDashoffset: [100, 0] }, { fill: '#b91e1e' }]}
        stroke="#b91e1e"
        strokeWidth="2"
        fill="none"
        d="M61.9,55.4c-2.3-3.5-3.6-7.7-3.6-12.2c0-4.7,1.5-9.1,4-12.7c2.1,3.1,3.4,6.7,3.7,10.7h13  C78.2,24,65,10.1,48.1,8.2l-3.8-6.6l-3.8,6.6C23.5,10.1,10.3,24,9.5,41.3h13c0.3-3.9,1.6-7.6,3.7-10.7c2.5,3.6,4,8,4,12.7  c0,4.5-1.4,8.7-3.7,12.2c-2.3-3.2-3.8-7-4-11.2h-13c0.8,18.5,16,33.3,34.7,33.3S78.2,62.7,79,44.3h-13  C65.7,48.4,64.2,52.2,61.9,55.4z M36,62.9c3.9-5.6,6.2-12.3,6.2-19.6c0-7.6-2.5-14.7-6.8-20.4c2.7-1.2,5.6-1.9,8.8-1.9  c3.1,0,6.1,0.7,8.8,1.9c-4.2,5.7-6.8,12.7-6.8,20.4c0,7.3,2.3,14.1,6.2,19.6c-2.5,1-5.3,1.6-8.2,1.6C41.3,64.5,38.6,63.9,36,62.9z"
      />
    </svg>
  );
};
```

### Bouncing Balls

The following example shows usage of percentage-based animation and negative delay support.

![Bouncing Balls](https://github.com/teodosii/react-tweenful/raw/master/gif/bouncing-ball.gif "Bouncing Balls")

```jsx
import React from 'react';
import { SVG, percentage, elastic } from 'react-tweenful';

const circles = new Array(10).fill(0).map((_e, i) => ({
  loop: true,
  fill: `hsl(${(i + 1) * 20 - 20}, 70%, 70%)`,
  delay: ((i + 1) * 1500) / -10,
  duration: 1500,
  easing: elastic(2, 0.9),
  transform: {
    translate: '0 100px'
  },
  style: {
    transformOrigin: `${-200 + 120 * (i + 1)}px 250px`
  },
  animate: percentage({
    '0%': { translate: '0px 100px', scale: 1 },
    '50%': { translate: '0px -100px', scale: 0.3 },
    '100%': { translate: '0px 100px', scale: 1 }
  }),
  r: 35,
  cx: 100 * i + 50,
  cy: 250
}));

const BouncingBalls = () => {
  return (
    <div className="bouncing-balls">
      <svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 1000 500">
        {circles.map((circle, i) => (
          <SVG.circle key={i} {...circle}></SVG.circle>
        ))}
      </svg>
    </div>
  );
};
```


### Conclusions

The library is released under MIT license so feel free to use it in any commercial product.

I hope you liked it and I'm looking forward for your feedback and suggestions.