5 современных способов создания анимации в React

Обложка поста

Создание анимации в приложении React — популярная тема. Многие разработчики анимируют только с помощью CSS, добавляя классы в теги HTML. Это отличный способ, но если хотите создавать сложные анимации, обратите внимание на другие подходы.

Давайте поговорим о них.

Примечание: весь код из статьи вы можете найти в этом репозитории.

1. Метод CSS

Это один из лучших методов для простой анимации. Если вы используете его вместо импорта библиотек JavaScript, сборка остаётся маленькой и браузер тратит меньше ресурсов. Это сильно влияет на производительность приложений. Если у вас простая анимация и вы беспокоитесь о размере своей сборки, обратите внимание на этот метод.

Давайте посмотрим на пример гамбургер-меню:

Это простое меню со свойством css и триггером className = "is-nav-open" для тега html. Способов реализации много. Один из них — создать wrapper над навигацией и вызывать изменения полей. Навигация имеет постоянную ширину, равную 250px, и wrapper со свойством margin-left или translateX с той же шириной. Если нужно показать навигацию, следует добавить className = "is-nav-open" для wrapper и переместить wrapper на margin-left / translateX: 0;

export default class ExampleCss extends Component {
    handleClick() {
        const wrapper = document.getElementById('wrapper');
        wrapper.classList.toggle('is-nav-open')
    }
    render() {
        return (
            <div id="wrapper" className="wrapper">
                <div className="nav">
                    <icon
                        className="nav__icon"
                        type="menu-fold"
                        onClick={() => this.handleClick()}/>
                    <div className="nav__body">
                        Lorem ipsum dolor sit amet, consectetur adipisicing elit.
                        Beatae ducimus est laudantium libero nam optio repellat
                        sit unde voluptatum?
                    </div>
                </div>
            </div>
        );
    }
}

И CSS стили:

.wrapper {
    display: flex;
    width: 100%;
    height: 100%;
    transition: margin .5s;
    margin: 0 0 0 -250px;
}

.wrapper.is-nav-open {
    margin-left: 0;
}

.nav {
    position: relative;
    width: 250px;
    height: 20px;
    padding: 20px;
    border-right: 1px solid #ccc;
}
.nav__icon {
    position: absolute;
    top: 0;
    right: -60px;
    padding: 20px;
    font-size: 20px;
    cursor: pointer;
    transition: color .3s;
}

.nav__icon:hover {
    color: #5eb2ff;
}

Этот метод необходим в большинстве случаев. Лучше написать несколько строк CSS и изменять className, чем импортировать и использовать большие библиотеки. А пользователи будут благодарны за то, что приложение быстро работает в браузере.

2. ReactTransitionGroup

Этот дополнительный компонент был разработан парнями из сообщества ReactJs. ReactTransitionGroup легко реализует базовые CSS-анимации и переходы.

Разработчики описывают эту библиотеку так:

Набор компонентов для управления состояниями с течением времени, специально разработанный для анимаций.

Три вещи, которые вы должны знать об этом компоненте:

  1. ReactTransitionGroup меняет классы при изменении жизненного цикла компонента. Анимированный стиль, в свою очередь, должен быть описан в классах CSS.
  2. ReactTransitionGroup имеет небольшой размер. Он должен быть установлен в пакете для React приложения и несущественно увеличит вашу сборку. Также можно использовать CDN.
  3. ReactTransitionGroup имеет 3 компонента (Transition, CSSTransition и TransitionGroup). Чтобы запустить анимацию, нужно обернуть компонент в них.

Посмотрим, как сделать похожую анимацию.

Сначала нужно импортировать CSSTransitionGroup из react-transition-group. Затем вы должны обернуть список и установить свойство transitionName. Каждый раз, когда добавляется или удаляется дочерний элемент в CSSTransitionGroup, он получает стили анимации.

<CSSTransitionGroup
    transitionName="example">
    {items}
</CSSTransitionGroup>

Если вы установили свойство transitionName = "example", классы в таблицах стилей должны начинаться с имени примера.

.example-eneter {
    opacity: 0.01;
}

.example-enter.example-enter-active {
    opacity: 1;
    transition: opacity 300ms ease-in;
}

.example-leave {
    opacity: 1;
}

.example-leave.example-leave-active {
    opacity: 0.01;
    transition: opacity 300ms ease-in;
}

Вы можете увидеть базовое использование ReactTransitionGroup.

Конечно, надо добавить немного логики. Следует описать два метода для реализации нашего примера списка контактов:

handleAdd — добавляет новые контакты, получает случайное имя и помещает его в массив state.items. (для случайного имени используется пакет random-name).

handleRemove — удаление контакта по индексу в массиве state.items.

import React, { Component, Fragment } from 'react';
import { CSSTransitionGroup } from 'react-transition-group'
import random from 'random-name'
import Button from './button'
import Item from './item'
import './style.css';

export default class ReactTransitionGroup extends Component {
    
    constructor(props) {
        super(props);
        this.state = { items: ['Natividad Steen']};
        this.handleAdd = this.handleAdd.bind(this);
    }

    handleAdd() {
        let newItems = this.state.items;
        newItems.push(random());
        this.setState({ items: newItems });
    }

    render () {
        const items = this.state.items.map((item, i) => (
            <Item
            item={item}
            key={i}
            keyDelete={i}
            handleRemove={(i) => this.handleRemove(i)}
            />
        ));

    return (
        <Fragment>
            <Button onClick={this.handleAdd}/>
                <div className="project">
                    <CSSTransitionGroup
                    transitionName="example"
                    transitionEnterTimeout={500}
                    transitionLeaveTimeout={300}
                    >
                        {items}
                    </CSSTransitionGroup>
                </div>
        </Fragment>
    );
}
};

3️. React-animations

React-animations — библиотека, построенная на анимациях из animate.css. Она проста в использовании и имеет множество коллекций анимации. React-animation работает с любой inline-style библиотекой, которая поддерживает использование объектов для определения ключевых кадров анимации, таких как Radium, Aphrodite или styled-components.

Можете посмотреть на анимации в гифке:

 

Давайте посмотрим, как это работает. Например, анимация подпрыгивания.

Сначала нужно импортировать выбранную анимацию из react-animations.

const Bounce = styled.div`animation: 2s ${keyframes`${bounce}`} infinite`;

Когда компонент создан, нужно обернуть любой HTML-код или компонент для анимации.

<bounce><h1>Hello Animation Bounce</h1></bounce>

Пример:

import React, { Component } from 'react';
import styled, { keyframes } from 'styled-components';
import { bounce } from 'react-animations';
import './style.css';

const Bounce = styled.div`animation: 2s ${keyframes`${bounce}`} infinite`;

export default class ReactAnimations extends Component {
    render() {
        return (
            <Bounce><h1>Hello Animation Bounce</h1></bounce>
        );
    }
}

Анимация работает. Она базовая и очень простая.

Есть хорошее решение для использования этой анимации при прокрутке — react-animate-on-scroll.

4️. React-reveal

React Reveal — это анимационный фреймворк для React. В нём есть основные анимации, такие как постепенное исчезновение, отражение, масштабирование, вращение и другие более сложные анимации. Он позволяет управлять всеми анимациями с помощью props, например, задавать дополнительные настройки: положение, задержка, расстояние, каскад и многие другие. Можно увидеть их здесь. Также можно использовать кастомные эффекты CSS, серверный рендеринг и компоненты высокого порядка. Если вы предпочитаете использовать анимацию прокрутки — этот фреймворк для вас.

import Fade from 'react-reveal/Fade';

<Fade top>
    <h1>Title</h1>
</Fade>

Давайте посмотрим на эту анимацию прокрутки.

У нас есть 5 блоков, у каждого из которых полноэкранная страница и заголовок внутри.

import React, { Component, Fragment } from 'react';
import Fade from 'react-reveal/Fade';

const animateList = [1, 2, 3, 4, 5];

export default class ReactReveal extends Component {
    render() {
        return (
            <Fragment>
                {animateList.map((item, key) => (
                    <div style={styles.block} key={key}>
                        <Fade top>
                            <h1 style={styles.title}>{`block ${item}`}</h1>                       
                        </Fade>
                    </div>
                ))}
            </Fragment>
        );
    }
}

const styles = {
    block: {
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        width: '100%',
        height: '100%',
        background: '#000',
        borderBottom: '1px solid rgba(255,255,255,.2)',
    },
    title: {
        textAlign: 'center',
        fontSize: 100,
        color: '#fff',
        fontFamily: 'Lato, sans-serif',
        fontWeight: 100,
    },
};

Мы создаём константу animateList. Этот массив содержит 5 элементов. После использования метода массива map вы можете рендерить каждый элемент в компонентах Fade и вставить элементы в заголовок. Стили, определённые в константе styles, имеют короткие стили CSS для блока и заголовка. У нас есть 5 блоков с анимацией Fade сверху.

5️. TweenOne и анимация в Ant Design

Ant Design — это библиотека React UI, представляющая собой множество простых в использовании компонентов. Это полезный компонент для создания элегантных пользовательских интерфейсов. Ant Design создан китайским конгломератом Alibaba, который использует его во многих своих проектах.

Давайте рассмотрим анимацию на их лендинге.

Как видите, здесь много анимированных элементов. Рассмотрим короткую версию, потому что у всех элементов одинаковая анимация. Наша анимация будет выглядеть так:

В этой анимации использован компонент TweenOne, но ему нужен PathPlugin для задания траектории. Это будет работать, когда вы поместите PathPlugin в TweenOne.plugins.

TweenOne.plugins.push(PathPlugin);

Основные параметры анимации:

  • duration — время анимации в мс;
  • ease — плавность анимации;
  • yoyo — чередование движения вперёд и назад с каждым повторением;
  • repeat — повтор анимации. Нужно использовать -1 для бесконечной анимации;
  • p — координаты пути для анимации;
  • easePath — координаты плавного пути для анимации.

Не нужно беспокоиться о последних двух параметрах, более специфичных для данного svg.

const duration = 7000;
const ease = 'easeInOutSine';
const p =
  'M123.5,89.5 C148,82.5 239.5,48.5 230,17.5 C220.5,-13.5 127,6 99.5,13.5 C72,21 -9.5,56.5 1.5,84.5 C12.5,112.5 99,96.5 123.5,89.5 Z';
const easePath =
  'M0,100 C7.33333333,89 14.3333333,81.6666667 21,78 C25.3601456,75.6019199 29.8706084,72.9026327 33,70 C37.0478723,66.2454406 39.3980801,62.0758689 42.5,57 C48,46.5 61.5,32.5 70,28 C77.5,23.5 81.5,20 86.5,16 C89.8333333,13.3333333 94.3333333,8 100,0';
const loop = {
  yoyo: true,
  repeat: -1,
  duration,
  ease,
};

Далее создадим объект анимации. Этот объект имеет 3 типа анимации:

  • redSquare — в нём есть параметры цикла, а также координата Y, длительность и задержка;
  • greenBall — имеет путь с параметрами объекта x, y — значение p. Длительность, повтор и плавность — TweenOne.easing.path — функция с двумя параметрами:
    • path — координата easePath;
    • lengthPixel — кривая, разделённая на 400 секций;
  • track — овал с осями, который имеет стили цикла и параметр поворота.
const animate = {
  redSquare: {
    ...loop,
    y: 15,
    duration: 3000,
    delay: 200,
  },
  greenBall: {
    path: { x: p, y: p },
    duration: 5000,
    repeat: -1,
    ease: TweenOne.easing.path(easePath, { lengthPixel: 400 }),
  },
  track: {
    ...loop,
    rotate: 15,
  },
};

Обратите внимание на компонент TweenOne. Напомним вкратце, эти компоненты будут импортированы из rc-tween-one. Он используется в качестве базового компонента с базовыми props и анимационными props. Это и есть наша анимация. Каждый TweenOne имеет свои собственные правила анимации, такие как redSquare, track, greenBall.

import React from 'react';
import TweenOne from 'rc-tween-one';

export default function BannerImage() {
    return (
      <div className="wrapper-ant-design">
        <svg width="482px" height="500px" viewBox="0 0 482 500">
          <defs>
            <path
              d="M151,55 C129.666667,62.6666667 116,74.3333333 110,90 C104,105.666667 103,118.5 107,128.5 L225.5,96 C219.833333,79 209.666667,67 195,60 C180.333333,53 165.666667,51.3333333 151,55 L137,0 L306.5,6.5 L306.5,156 L227,187.5 L61.5,191 C4.5,175 -12.6666667,147.833333 10,109.5 C32.6666667,71.1666667 75,34.6666667 137,0 L151,55 Z"
              id="mask"
            />
          </defs>
          <g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd" transform="translate(0, 30)">
            <g id="Group-13" transform="translate(0.000000, 41.000000)">
              <TweenOne component="g" animation={animate.redSquare}>
                <rect
                  stroke="#F5222D"
                  strokeWidth="1.6"
                  transform="translate(184.000000, 18.000000) rotate(8.000000) translate(-184.000000, -18.000000) "
                  x="176.8"
                  y="150.8"
                  width="14.4"
                  height="14.4"
                  rx="3.6"
                />
              </TweenOne>
            </g>
            <g id="Group-14" transform="translate(150.000000, 230.000000)">
              <g id="Group-22" transform="translate(62.000000, 7.000000)">
                <image
                  id="cc4"
                  alt="globe"
                  xlinkHref="https://gw.alipayobjects.com/zos/rmsportal/FpKOqFadwoFFIZFExjaf.png"
                  width="151px"
                  height="234px"
                />
              </g>
              <mask id="mask-2">
                <use xlinkHref="#mask" fill="white" transform="translate(-42, -33)" />
              </mask>
              <g mask="url(#mask-2)">
                <TweenOne component="g" animation={animate.track} style={{ transformOrigin: '122.7px 58px' }}>
                  <g transform="translate(-16, -52)">
                    <g transform="translate(16, 52)">
                      <path
                        d="M83.1700911,35.9320015 C63.5256194,37.9279025 44.419492,43.1766434 25.8517088,51.6782243 C14.3939956,57.7126276 7.77167019,64.8449292 7.77167019,72.4866248 C7.77167019,94.1920145 61.1993389,111.787709 127.105708,111.787709 C193.012078,111.787709 246.439746,94.1920145 246.439746,72.4866248 C246.439746,55.2822262 212.872939,40.6598106 166.13127,35.3351955"
                        id="line-s"
                        stroke="#0D1A26"
                        strokeWidth="1.35"
                        strokeLinecap="round"
                        transform="translate(127.105708, 73.561453) rotate(-16.000000) translate(-127.105708, -73.561453) "
                      />
                    </g>
                    <TweenOne component="g" animation={animate.greenBall}>
                      <image
                        alt="globe"
                        id="id2"
                        xlinkHref="https://gw.alipayobjects.com/zos/rmsportal/IauKICnGjGnotJBEyCRK.png"
                        x="16"
                        y="62"
                        width="26px"
                        height="26px"
                      />
                    </TweenOne>
                  </g>
                </TweenOne>
              </g>
            </g>
          </g>
        </svg>
      </div>
    );
  }

Выглядит пугающе. Но на самом деле нужно обратить внимание на эти строки.

  <TweenOne component="g" animation={animate.redSquare} />
  <TweenOne component="g" animation={animate.track} />
  <TweenOne component="g" animation={animate.greenBall} />

Анимировать с помощью этого метода довольно просто. Всё, что нужно, это описать правила анимации и перенести их в компонент TweenOne.

Заключение

Анимации для разных целей требуют разных подходов. Мы рассмотрели несколько решений, которые вы можете использовать в своих проектах. Выберите метод, который подходит вам.

 

Ниже вы найдёте бонус со списком популярных библиотек для анимации.

Библиотеки ReactJs

  1. react-motion — фреймворк, который решает проблемы с анимацией.
  2. react-spring — основанная на физике библиотека анимации React.
  3. ant-motion — спецификация Animate и компоненты Ant Design.
  4. react-move — красивые анимации, управляемые данными для React.
  5. react-flight — лучший способ создания анимационных композиций для React.
  6. react-flip-move — простая анимация между изменениями DOM (например, упорядочивание списка) с использованием техники FLIP.
  7. react-burger-menu — компонент боковой панели с коллекцией эффектов и стилей, использующих переходы CSS и анимацию пути SVG.
  8. animated — Библиотека Декларативных Анимаций для React и React Native.
  9. react-tween-state — анимация React.
  10. react-animations — коллекция анимаций для inline-style библиотек.

Библиотеки Javascript

  1. GSAP — ультра высокопроизводительная профессиональная анимация для современного Интернета.
  2. Anime.js — Anime.js (/ˈæn.ə.meɪ/) – это лёгкая библиотека анимации JavaScript с простым, но мощным API. Работает со свойствами CSS, SVG, атрибутами DOM и объектами JavaScript.
  3. Popmotion — простые библиотеки анимации для восхитительных пользовательских интерфейсов.
  4. vivus — библиотека JavaScript для рисования анимации в SVG.
  5. svg.js — лёгкая библиотека для управления и анимации SVG.
  6. velocity — это анимационный движок с тем же API, что и у jQuery $.animate().
  7. wow — показывает анимации при прокрутке. Дружит с animate.css.
  8. dynamic.js — библиотека JavaScript для создания анимации на основе физики.
  9. granim.js — создавайте плавные и интерактивные градиентные анимации с помощью этой небольшой библиотеки Javascript.
  10. kute.js — это встроенный движок JavaScript-анимации с отличным качеством кода и хорошей производительностью.
  11. TweenJs — простая, но мощная библиотека межкадрового заполнения / анимации для Javascript. Часть набора библиотек CreateJS.
  12. moveTo — лёгкая JavaScript библиотека анимации прокрутки без зависимостей.

Используйте анимацию с умом!

Перевод статьи «5 Ways to animate a React app in 2019»

Иван Капцов

Как Яндекс использует ваши данные и машинное обучение для персонализации сервисов — читать и смотреть YaC 2019.