> Как решать задачу анимации шаг за шагом (JavaScript)

Уровень: senior · Роль: frontend · Категория: Технические вопросы

Компании: MTS

Стек: JavaScript

> Пример ответа

Короткий ответ

Анимация шаг за шагом решается через requestAnimationFrame, который синхронизирует кадры с обновлением экрана. В каждом кадре вычисляется прогресс анимации на основе времени, обновляется состояние (позиция, opacity, transform) и применяются изменения к DOM или canvas. Для сложных сценариев используют библиотеки вроде GSAP или Framer Motion, но базовый цикл остаётся тем же: инициализация, обновление, рендер.

Подробное объяснение

Анимация шаг за шагом - это ручное управление каждым кадром, в отличие от CSS-анимаций или декларативных библиотек. Основной инструмент - requestAnimationFrame (rAF), который вызывает колбэк перед следующей отрисовкой браузера. Внутри колбэка:

  1. Вычисляется deltaTime - разница во времени между текущим и предыдущим кадром. Это критично для плавности: анимация должна быть независима от FPS.
  2. На основе deltaTime и длительности анимации рассчитывается прогресс (от 0 до 1). Можно использовать easing-функции для нелинейного движения.
  3. Прогресс преобразуется в конкретные значения свойств: позиция, поворот, прозрачность.
  4. Эти значения применяются к элементу (через style, transform, canvas API).
  5. Если анимация не завершена, рекурсивно вызывается rAF.

Важно: при паузе или переключении вкладок rAF автоматически приостанавливается, что экономит ресурсы. Для анимаций на canvas или WebGL подход тот же, но рендер идёт через контекст.

На практике

Начинаем с определения начального и конечного состояния, длительности и easing. Затем пишем функцию animate, которая принимает эти параметры и возвращает промис для отслеживания завершения. Внутри используем performance.now() для точного времени, а не Date.now(). Для отмены анимации сохраняем ID из requestAnimationFrame и вызываем cancelAnimationFrame.

Типичный сценарий: анимация появления модального окна (fade in + slide up). В каждом кадре обновляем opacity и transform: translateY. Для сложных цепочек анимаций (sequence) можно создать очередь или использовать async/await с промисами.

Пример кода

JAVASCRIPT
function animate(options) {
const { from, to, duration, easing = t => t, onUpdate, onComplete } = options;
const startTime = performance.now();
let rafId;
function frame(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easing(progress);
const currentValue = from + (to - from) * easedProgress;
onUpdate(currentValue);
if (progress < 1) {
rafId = requestAnimationFrame(frame);
} else {
onComplete?.();
}
}
rafId = requestAnimationFrame(frame);
return {
cancel: () => cancelAnimationFrame(rafId)
};
}
// Использование
const anim = animate({
from: 0,
to: 100,
duration: 1000,
easing: t => t * (2 - t), // easeOutQuad
onUpdate: val => {
element.style.transform = `translateX(${val}px)`;
},
onComplete: () => console.log('done')
});

Как отвечать на собеседовании

Начни с объяснения, что requestAnimationFrame - основа ручной анимации, и почему он лучше setTimeout/setInterval (синхронизация с vsync, экономия батареи). Упомяни расчёт deltaTime для независимости от FPS. Затем опиши структуру цикла: получение времени, вычисление прогресса, применение easing, обновление DOM. Если спросят про сложные сценарии - расскажи про композицию анимаций через промисы или библиотеки. Покажи понимание trade-off: rAF даёт контроль, но требует больше кода, чем CSS-анимации. Избегай излишней детализации про конкретные библиотеки, если не спросят.

Что проверяет интервьюер

  • Понимание event loop и работы requestAnimationFrame
  • Умение работать с временем и производительностью (избегать jank)
  • Знание easing-функций и их влияния на восприятие
  • Способность организовать код анимации без библиотек
  • Понимание разницы между анимацией через JS и CSS
  • Навыки работы с промисами для управления последовательностью

Типичные ошибки

  • Использование setInterval или setTimeout для анимации - они не синхронизированы с отрисовкой и вызывают пропуски кадров
  • Игнорирование deltaTime - анимация ускоряется/замедляется при изменении FPS
  • Неправильное округление значений - приводит к дрожанию (jitter) на экранах с высокой плотностью пикселей
  • Забывание отмены requestAnimationFrame при размонтировании компонента - утечка памяти
  • Применение стилей, которые вызывают reflow (например, top/left вместо transform) - падение производительности
  • Смешивание разных единиц измерения при интерполяции (px и %)

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью