Интеграция anime.js и canvas API

Библиотека anime.js управляет анимацией через изменение свойств объектов. Canvas API, в отличие от DOM, не хранит состояние сцены — отрисовка выполняется вручную на каждом кадре. Интеграция строится на связке:

  • anime.js — вычисляет промежуточные значения
  • CanvasRenderingContext2D — отрисовывает кадр на основе этих значений
  • requestAnimationFrame — синхронизирует обновление экрана

Ключевой принцип: anime.js не работает напрямую с canvas-элементом, а изменяет JavaScript-объекты, которые затем используются для рендеринга.


Базовая структура проекта

<canvas id="scene"></canvas>
const canvas = document.getElementById('scene');
const ctx = canvas.getContext('2d');

canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

const state = {
  x: 100,
  y: 100,
  radius: 50
};

Объект state выступает связующим звеном между anime.js и Canvas API.


Простейшая анимация объекта

anime({
  targets: state,
  x: 500,
  duration: 2000,
  easing: 'easeInOutQuad',
  update: draw
});

function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  ctx.beginPath();
  ctx.arc(state.x, state.y, state.radius, 0, Math.PI * 2);
  ctx.fill();
}

Ключевые моменты:

  • targets указывает на объект состояния
  • update вызывается на каждом кадре
  • canvas очищается перед каждой отрисовкой

Управление циклом отрисовки

Использование update подходит для простых случаев, но для сложных сцен эффективнее отделить рендеринг:

function render() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  drawCircle();
  requestAnimationFrame(render);
}

render();

А anime.js обновляет только значения:

anime({
  targets: state,
  x: 600,
  duration: 3000,
  easing: 'linear'
});

Такой подход:

  • уменьшает нагрузку
  • упрощает масштабирование сцены
  • позволяет комбинировать несколько анимаций

Анимация нескольких объектов

const particles = Array.from({ length: 50 }, () => ({
  x: Math.random() * canvas.width,
  y: Math.random() * canvas.height,
  radius: Math.random() * 5 + 2
}));

Анимация:

anime({
  targets: particles,
  y: () => Math.random() * canvas.height,
  delay: anime.stagger(50),
  loop: true,
  direction: 'alternate'
});

Рендеринг:

function drawParticles() {
  particles.forEach(p => {
    ctx.beginPath();
    ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
    ctx.fill();
  });
}

Интерполяция сложных свойств

anime.js позволяет анимировать не только числа:

const state = {
  color: 'rgb(255, 0, 0)'
};

anime({
  targets: state,
  color: 'rgb(0, 0, 255)',
  duration: 2000,
  easing: 'linear'
});

Использование в canvas:

ctx.fillStyle = state.color;

Анимация трансформаций

Canvas не имеет встроенной модели трансформаций объектов, поэтому они применяются вручную:

const state = {
  x: 200,
  y: 200,
  rotation: 0
};

anime({
  targets: state,
  rotation: Math.PI * 2,
  duration: 3000,
  loop: true,
  easing: 'linear'
});

Отрисовка:

function drawRotated() {
  ctx.save();

  ctx.translate(state.x, state.y);
  ctx.rotate(state.rotation);

  ctx.fillRect(-50, -50, 100, 100);

  ctx.restore();
}

Таймлайны для сложных сцен

const tl = anime.timeline({
  loop: true
});

tl.add({
  targets: state,
  x: 400,
  duration: 1000
}).add({
  targets: state,
  y: 300,
  duration: 1000
}).add({
  targets: state,
  x: 100,
  duration: 1000
});

Преимущества:

  • последовательная анимация
  • синхронизация нескольких параметров
  • читаемость сложных сценариев

Оптимизация производительности

Очистка только нужной области

ctx.clearRect(state.x - 60, state.y - 60, 120, 120);

Использование offscreen canvas

const offCanvas = document.createElement('canvas');
const offCtx = offCanvas.getContext('2d');

Позволяет:

  • кэшировать сложные формы
  • уменьшить количество вычислений

Работа с событиями

anime.js можно связывать с пользовательскими действиями:

canvas.addEventListener('click', (e) => {
  anime({
    targets: state,
    x: e.clientX,
    y: e.clientY,
    duration: 1000,
    easing: 'easeOutElastic'
  });
});

Синхронизация с физикой

Интеграция с физическими расчетами:

const state = {
  x: 100,
  velocity: 0
};

function updatePhysics() {
  state.velocity += 0.5; // гравитация
  state.y += state.velocity;
}

Anime.js может управлять начальными условиями:

anime({
  targets: state,
  velocity: -10,
  duration: 500
});

Комбинирование с requestAnimationFrame

Оптимальная архитектура:

function loop() {
  updatePhysics();
  draw();
  requestAnimationFrame(loop);
}

loop();

anime.js:

  • изменяет параметры
  • не отвечает за рендеринг

Canvas:

  • отвечает только за визуализацию

Частые ошибки

1. Отсутствие очистки canvas

Приводит к “шлейфам” и артефактам.

2. Слишком частое создание объектов

// плохо
anime({
  targets: { x: 0 }
});

Каждый раз создаётся новый объект.

3. Логика внутри draw()

Рендеринг должен быть максимально “чистым”:

  • без вычислений
  • без изменений состояния

Расширенные техники

Шейпы и Path2D

const path = new Path2D();
path.arc(0, 0, 50, 0, Math.PI * 2);

Градиенты

const gradient = ctx.createLinearGradient(0, 0, 200, 0);
gradient.addColorStop(0, 'red');
gradient.addColorStop(1, 'blue');

Анимация:

anime({
  targets: gradient,
  // косвенное управление через параметры
});

Архитектурный паттерн

Рекомендуемая структура:

  • state — хранит данные
  • anime.js — изменяет state
  • render() — отображает state
  • loop() — управляет обновлением
const state = {};
function update() {}
function render() {}
function loop() {
  update();
  render();
  requestAnimationFrame(loop);
}

Такой подход обеспечивает:

  • масштабируемость
  • предсказуемость
  • контроль над производительностью