Как сделать JS анимацию с помощью Anime.js

JavaScript
19702 просмотра

Среди JavaScript-библиотек для анимации Anime.js занимает особое место: она весит меньше 17 КБ, покрывает практически любой сценарий — от простого перемещения блока до синхронизированных таймлайнов — и при этом читается как обычный JavaScript, а не как заклинание на непонятном языке. Anime.js доступна на GitHub, поддерживается всеми современными браузерами и активно развивается.

Anime.js — JavaScript-библиотека для веб-анимаций

В этой статье разберём, как делать JavaScript-анимации на сайте с помощью Anime.js — от подключения до сложных таймлайнов — с живыми примерами прямо на странице. Можно нажимать «Перезапустить» столько раз, сколько захочется.

Подключение

Два способа на выбор. Если проект простой или нужно быстро попробовать — CDN:

<!-- Перед </body> -->
<script src="https://cdn.jsdelivr.net/npm/animejs@3/lib/anime.min.js"></script>

Для проектов с npm/Yarn:

npm install animejs
# или
yarn add animejs

После установки импортируйте библиотеку в нужном модуле:

// ES-модуль
import anime from 'animejs';

Первая анимация

Вся работа с Anime.js строится вокруг одной функции — anime() . Она принимает объект с описанием анимации и запускает её:

anime({
    /* описание анимации */
});

Внутри объекта — четыре группы свойств:

  • Цели ( targets ) — что анимируем. CSS-селектор, DOM-элемент, NodeList или JavaScript-объект.
  • Свойства — что именно меняется: CSS-свойства, трансформации, атрибуты SVG, числовые поля объектов.
  • Параметры свойств — duration , delay , easing и другие — задаются для каждого свойства отдельно или глобально.
  • Параметры анимации — direction , loop , autoplay .

Посмотрим на практике. Зелёный квадрат смещается вправо и превращается в круг — затем возвращается обратно:

anime({
    targets: '.box',
    translateX: 120,       // сместить на 120px вправо
    borderRadius: '50%',   // превратить в круг
    duration: 1800,
    easing: 'easeInOutSine',
    direction: 'alternate', // проиграть в обратном порядке после завершения
    loop: true,
});
<div class="box"></div>
.box {
    width: 60px;
    height: 60px;
    background-color: hsl(140, 60%, 50%);
    border-radius: 4px;
}

Обратите внимание — единицы измерения писать не нужно, Anime.js добавляет их автоматически. Если нужно что-то нестандартное — "120rem" или "50%" — просто оберните значение в строку.

Функции плавности в Anime.js

Easing — это кривая скорости анимации: с каким ускорением объект начинает движение и замедляется в конце. Anime.js включает полный арсенал встроенных функций:

  • Линейные: linear
  • Стандартные кривые: easeInSine , easeOutSine , easeInOutSine — и аналоги для Quad, Cubic, Quart, Quint, Expo, Circ, Back, Elastic, Bounce
  • Пружинная: easeOutElastic(1, .5) — с настраиваемой амплитудой и периодом
  • Шаги: steps(5) — дискретная анимация, как в ретро-играх
  • Кубическая кривая Безье: cubicBezier(.5, .05, .1, .3)
Для имитации физически достоверных движений чаще всего используют easeInOutSine и easeOutElastic. Для UI-переходов кнопок и карточек — easeOutQuad. linear подходит там, где нет резких остановок: бесконечные вращения, progress-бары.

Пример: анимация маятника

Маятник — хороший пример физически достоверной анимации. Ключевой приём здесь — задать диапазон значений вместо одного числа и использовать easeInOutSine , который точно воспроизводит замедление на пиках и ускорение внизу:

anime({
    targets: '#rod',
    rotate: [50, -50],    // от +50° до -50°
    duration: 2000,
    easing: 'easeInOutSine',
    direction: 'alternate',
    loop: true,
});
<div class="pendulum-wrap">
    <div class="pendulum-pivot"></div>
    <div id="rod">
        <div class="pendulum-ball"></div>
    </div>
</div>
.pendulum-wrap {
    display: flex;
    justify-content: center;
}

.pendulum-pivot {
    width: 12px;
    height: 12px;
    background-color: hsl(210, 20%, 60%);
    border-radius: 50%;
    position: absolute;
}

#rod {
    width: 4px;
    height: 120px;
    background-color: hsl(210, 20%, 60%);
    transform-origin: top center;
}

.pendulum-ball {
    width: 32px;
    height: 32px;
    background-color: hsl(40, 90%, 60%);
    border-radius: 50%;
    margin: 0 auto;
    transform: translateX(-14px);
}

Параметр direction: alternate заставляет анимацию воспроизводиться в обратном порядке после завершения — именно это создаёт эффект маятника, а не телеграфного столба.

Stagger — анимация нескольких элементов

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

Вместо того чтобы прописывать delay вручную для каждого элемента, достаточно одной строки:

// Каждый следующий элемент стартует на 150 мс позже предыдущего
delay: anime.stagger(150)

// Задержка нарастает от центра к краям
delay: anime.stagger(100, { from: 'center' })

// Обратный порядок: последний элемент стартует первым
delay: anime.stagger(200, { direction: 'reverse' })

По умолчанию нулевую задержку получает первый элемент, каждый следующий ждёт чуть дольше. Параметр from меняет, с какого элемента начинается отсчёт: 'center' — волна расходится от середины к краям, 'last' — с конца к началу. Можно передать конкретный индекс — from: 3 — тогда нулевую задержку получит четвёртый элемент, а остальные будут ждать пропорционально расстоянию от него. direction: "reverse" работает иначе: он не меняет точку отсчёта, а просто переворачивает порядок — последний стартует первым.

anime({
    targets: '.wave-bar',
    scaleY: [0, 8, 0],
    backgroundColor: ['hsl(210, 70%, 60%)', 'hsl(270, 80%, 72%)', 'hsl(210, 70%, 60%)'],
    duration: 1400,
    delay: anime.stagger(80, { from: 'center' }),
    easing: 'easeInOutSine',
    loop: true,
});
<div class="wave-wrap">
    <div class="wave-bar"></div>
    <div class="wave-bar"></div>
    <!-- ещё 10 таких же... -->
</div>
.wave-wrap {
    display: flex;
    align-items: center;
    gap: 5px;
}

.wave-bar {
    width: 10px;
    height: 10px;
    background-color: hsl(210, 70%, 60%);
    border-radius: 5px;
    transform: scaleY(0); /* скрыть до старта анимации */
    transform-origin: center;
}

Пример: анимация зарядки аккумулятора

Три сегмента батареи расширяются один за другим. anime.stagger(500) добавляет 500 мс между стартом каждого сегмента. Вторая анимация считает значение прогресса от 0 до 100 и выводит его в элемент — через callback update , который вызывается на каждом кадре:

const battery = { progress: 0 };

// Анимация сегментов — справа налево через direction: 'reverse'
anime({
    targets: '.segment',
    scaleX: [0, 1],
    duration: 300,
    delay: anime.stagger(500, { direction: 'reverse' }),
    endDelay: 600,
    easing: 'linear',
    loop: true,
});

// Счётчик синхронизирован по длительности с анимацией сегментов
const battery = { progress: 0 };
anime({
    targets: battery,
    progress: [0, 100],
    duration: 1300,
    endDelay: 600,
    easing: 'linear',
    round: 1,
    loop: true,
    update: () => {
        document.querySelector('.battery-progress').textContent = battery.progress + '%';
    },
});
<div class="battery-wrap">
    <div class="battery">
        <div class="battery-tip"></div>
        <div class="battery-body">
            <div class="segment"></div>
            <div class="segment"></div>
            <div class="segment"></div>
        </div>
    </div>
    <div class="battery-progress">0%</div>
</div>
.battery-tip {
    width: 8px;
    height: 24px;
    border-radius: 4px 0 0 4px;
}

.battery-body {
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 6px;
    border: 3px solid #888;
    border-radius: 0 6px 6px 0;
}

.segment {
    width: 24px;
    height: 32px;
    border-radius: 3px;
    transform: scaleX(0);
    transform-origin: right center; /* раскрывается справа налево */
}
0%

Параметр round: 1 округляет анимируемое значение до целых чисел — без него счётчик показывал бы дробные проценты. Оба вызова anime() работают независимо, но с одинаковой суммарной длительностью (1300 + 600 мс), поэтому цифра и сегменты остаются в синхронизации бесконечно.

Кейфреймы — многоступенчатые анимации

Базовая анимация переводит элемент из точки А в точку Б. Кейфреймы позволяют задать произвольный маршрут: А → Б → В → Г. Для каждого свойства передаётся массив объектов, где каждый объект — один шаг с собственными duration и delay :

anime({
    targets: '#dot',
    translateY: [
        { value: -36, duration: 400 },
        { value: 0,   duration: 400, delay: 800 },
        { value: 36,  duration: 400, delay: 400 },
        { value: 0,   duration: 400, delay: 800 },
    ],
    translateX: [
        { value: 36,  duration: 400, delay: 400 },
        { value: 0,   duration: 400, delay: 800 },
        { value: -36, duration: 400, delay: 400 },
        { value: 0,   duration: 400, delay: 800 },
    ],
    easing: 'easeInOutQuad',
    loop: true,
});
<div class="keyframes-wrap">
    <div id="dot"></div>
</div>
.keyframes-wrap {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 120px;
}

#dot {
    width: 20px;
    height: 20px;
    background-color: hsl(200, 80%, 60%);
    border-radius: 50%;
}

Кейфреймы translateY и translateX выполняются параллельно: оба массива отсчитывают время независимо с самого начала. Именно из комбинации горизонтального и вертикального движений складывается обход по периметру. Это требует аккуратных расчётов duration и delay — но результат стоит усилий.

Текстовые JS-анимации

Anime.js не знает ничего о тексте — но умеет анимировать любой DOM-элемент. Разобьём слово на отдельные <span> и применим stagger для каскадного эффекта. Функция anime.stagger([0.5, 1], { from: 'center' }) задаёт диапазон масштаба — буквы в середине меньше, по краям крупнее:

anime({
    targets: '.letter',
    opacity: [0, 1],
    translateY: [40, 0],
    rotate: {
        value: 360,
        duration: 1600,
        easing: 'easeInExpo',
    },
    scale: anime.stagger([0.5, 1], { from: 'center' }),
    delay: anime.stagger(120, { start: 300 }),
    easing: 'easeOutExpo',
    duration: 1200,
});
<div class="text-wrap">
    <span class="letter">A</span>
    <span class="letter">n</span>
    <span class="letter">i</span>
    <span class="letter">m</span>
    <span class="letter">e</span>
    <span class="letter">.</span>
    <span class="letter">j</span>
    <span class="letter">s</span>
</div>
.text-wrap {
    display: flex;
    justify-content: center;
    font-size: 2.5em;
    font-weight: 700;
}

.letter {
    display: inline-block;
    opacity: 0;
}
A n i m e . j s

Обратите внимание: opacity: [0, 1] — это запись «от → до». Аналогичная нотация работает для любого свойства, когда нужно явно задать начальное значение вместо того, чтобы оно читалось из CSS.

Таймлайны в Anime.js

Таймлайн — это инструмент для координации нескольких анимаций во времени. Анимации добавляются методом .add() и по умолчанию выстраиваются последовательно. Второй аргумент .add() задаёт смещение относительно предыдущей анимации: '-=700' означает «начать на 700 мс раньше конца предыдущей»:

const tl = anime.timeline({
    duration: 800,
    easing: 'easeInOutSine',
    loop: true,
});

tl.add({
    targets: '.ball-one',
    keyframes: [{ translateY: -60 }, { translateY: 0 }],
}).add({
    targets: '.ball-two',
    keyframes: [{ translateY: -60 }, { translateY: 0 }],
}, '-=700').add({         // старт на 700мс раньше конца предыдущей
    targets: '.ball-three',
    keyframes: [{ translateY: -60 }, { translateY: 0 }],
}, '-=700');
<div class="balls-wrap">
    <div class="ball-one"></div>
    <div class="ball-two"></div>
    <div class="ball-three"></div>
</div>
.balls-wrap {
    display: flex;
    justify-content: center;
    align-items: flex-end;
    gap: 24px;
    height: 100px;
}

.ball-one,
.ball-two,
.ball-three {
    width: 32px;
    height: 32px;
    border-radius: 50%;
}

.ball-one   { background-color: hsl(200, 80%, 60%); }
.ball-two   { background-color: hsl(270, 80%, 72%); }
.ball-three { background-color: hsl(340, 80%, 65%); }

Глобальные параметры таймлайна — duration , easing , loop — наследуются всеми добавленными анимациями, но могут быть переопределены в каждой по отдельности.

Callbacks и управление JS-анимацией

Anime.js предоставляет набор callback-функций, которые срабатывают в ключевые моменты анимации:

anime({
    targets: '.box',
    translateX: 200,
    begin: (anim) => {
        // Вызывается один раз перед первым кадром
        console.log('Анимация началась');
    },
    update: (anim) => {
        // Вызывается на каждом кадре
        console.log('Прогресс:', anim.progress + '%');
    },
    complete: (anim) => {
        // Вызывается после последнего кадра
        console.log('Готово!');
    },
    loopBegin: (anim) => { /* Начало каждой итерации loop */ },
    loopComplete: (anim) => { /* Конец каждой итерации loop */ },
    changeBegin: (anim) => { /* Перед изменением значений */ },
    changeComplete: (anim) => { /* После изменения значений */ },
});

Методы управления воспроизведением — вызываются на экземпляре анимации:

const anim = anime({ targets: '.box', translateX: 200 });

anim.play();           // запустить / продолжить
anim.pause();          // поставить на паузу
anim.restart();        // сбросить и проиграть с начала
anim.reverse();        // изменить направление
anim.seek(1000);       // перейти к отметке 1000мс
anim.seek(anim.duration); // перейти в конечное состояние

SVG-анимации

Anime.js отлично работает с SVG. Два наиболее востребованных сценария — анимация по пути и морфинг форм.

Анимация по пути

Метод anime.path() превращает SVG-путь в функцию, которая возвращает координаты и угол поворота в любой точке:

const path = anime.path('.track');

anime({
    targets: '.dot',
    translateX: path('x'),     // смещение по X вдоль пути
    translateY: path('y'),     // смещение по Y вдоль пути
    rotate: path('angle'),     // угол касательной — эллипс сам поворачивается
    duration: 3000,
    easing: 'linear',
    loop: true,
});
<svg viewBox="0 0 260 120" xmlns="http://www.w3.org/2000/svg">
    <!-- видимый пунктирный путь -->
    <path class="track"
        d="M 10,60 C 55,5 95,115 130,60 C 165,5 205,115 250,60" />
    <!-- элемент, который едет по пути; cx/cy = начало пути -->
    <ellipse class="dot" rx="14" ry="7" cx="10" cy="60" />
</svg>
.track {
    fill: none;
    stroke: #7a90b0;
    stroke-width: 2;
    stroke-dasharray: 6 5;
    opacity: 0.4;
}

.dot {
    fill: hsl(270, 80%, 72%);
    filter: drop-shadow(0 0 8px hsl(270, 80%, 72%));
    transform-box: fill-box;   /* transform-origin считается от bbox элемента */
    transform-origin: center;
}

Морфинг форм

Пять звёзд рейтинга вырастают из крошечной звезды в полноразмерную и одновременно меняют цвет с серого на золотой — одна за другой через stagger . Важное условие для корректного морфинга атрибута d : оба пути должны содержать одинаковое количество точек — здесь по 10 в каждом.

anime({
    targets: '.star',
    d: [{ value: 'M 24,2 L 29.3,16.7 L 44.9,17.2 L 32.6,26.8 L 36.9,41.8 L 24,33 L 11.1,41.8 L 15.4,26.8 L 3.1,17.2 L 18.7,16.7 Z' }],
    fill: 'hsl(45, 95%, 55%)',
    duration: 500,
    delay: anime.stagger(200),
    easing: 'easeOutBack',
    direction: 'alternate',
    loop: true,
});
<div class="stars-wrap">
    <svg class="star-svg" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
        <path class="star" d="M 24,14 L 26.4,20.8 L 33.5,20.9 L 27.8,25.2 L 29.9,32.1 L 24,28 L 18.1,32.1 L 20.2,25.2 L 14.5,20.9 L 21.6,20.8 Z" />
    </svg>
    <svg class="star-svg" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
        <path class="star" d="M 24,14 L 26.4,20.8 L 33.5,20.9 L 27.8,25.2 L 29.9,32.1 L 24,28 L 18.1,32.1 L 20.2,25.2 L 14.5,20.9 L 21.6,20.8 Z" />
    </svg>
    <svg class="star-svg" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
        <path class="star" d="M 24,14 L 26.4,20.8 L 33.5,20.9 L 27.8,25.2 L 29.9,32.1 L 24,28 L 18.1,32.1 L 20.2,25.2 L 14.5,20.9 L 21.6,20.8 Z" />
    </svg>
    <svg class="star-svg" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
        <path class="star" d="M 24,14 L 26.4,20.8 L 33.5,20.9 L 27.8,25.2 L 29.9,32.1 L 24,28 L 18.1,32.1 L 20.2,25.2 L 14.5,20.9 L 21.6,20.8 Z" />
    </svg>
    <svg class="star-svg" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
        <path class="star" d="M 24,14 L 26.4,20.8 L 33.5,20.9 L 27.8,25.2 L 29.9,32.1 L 24,28 L 18.1,32.1 L 20.2,25.2 L 14.5,20.9 L 21.6,20.8 Z" />
    </svg>
</div>
.stars-wrap {
    display: flex;
    justify-content: center;
    gap: 8px;
}

.star-svg {
    width: 48px;
    height: 48px;
}

.star {
    fill: hsl(45, 10%, 70%);
}

Производительность JS-анимаций: советы

  • Анимируйте transform и opacity. Браузер обрабатывает их на GPU, не вызывая reflow. Анимация width , height , top , left — дорогая операция.
  • Используйте will-change: transform с умом. Только для элементов, которые анимируются точно и часто — иначе лишняя нагрузка на память.
  • Уважайте prefers-reduced-motion . Часть пользователей отключает анимации на системном уровне — медицинские причины, усталость. Проверяйте это медиа-запросом и убирайте анимации или заменяйте мгновенными переходами.
  • Pause при уходе вкладки. Подписывайтесь на visibilitychange и вызывайте anim.pause() / anim.play() — иначе анимации продолжают работать вхолостую.
// Уважаем настройку системы
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

if (!prefersReducedMotion) {
    anime({ targets: '.hero', opacity: [0, 1], translateY: [30, 0] });
}

// Пауза при скрытой вкладке
const anim = anime({ targets: '.loader', rotate: 360, loop: true });
document.addEventListener('visibilitychange', () => {
    document.hidden ? anim.pause() : anim.play();
});

Anime.js — одна из немногих библиотек, которая покрывает весь диапазон задач: от анимации кнопки при ховере до синхронизированного таймлайна с десятками элементов. Маленький размер, чистый API и активная поддержка делают её отличным выбором для любого проекта. Осталось дать волю воображению.

Если хочется анимировать элементы по скроллу без написания JavaScript — посмотрите на AOS.js: анимация при прокрутке.

Вам может быть интересно:
Swiper.js — слайдер для сайта: настройки и примеры
Swiper.js — как подключить и настроить слайдер на сайте
Что такое функция в программировании — типы и примеры на JavaScript и Python
Что такое функция в программировании?
Promise в JavaScript — промисы JS простыми словами для начинающих
Promise в JavaScript простыми словами — полное руководство
Анимация частиц и GLSL-шейдеры в PixiJS 8 — живой пример в браузере
Изучаем PixiJS. Часть 2 — продвинутая анимация и GLSL-шейдеры
Комментарии 0 Разные мнения приветствуются. Здесь можно спорить и обсуждать, но уважительно и без токсичности.
Отмена