> Как решать задачу анимации шаг за шагом (JavaScript)
Уровень: senior · Роль: frontend · Категория: Технические вопросы
Компании: MTS
Стек: JavaScript
> Пример ответа
Короткий ответ
Анимация шаг за шагом решается через requestAnimationFrame, который синхронизирует кадры с обновлением экрана. В каждом кадре вычисляется прогресс анимации на основе времени, обновляется состояние (позиция, opacity, transform) и применяются изменения к DOM или canvas. Для сложных сценариев используют библиотеки вроде GSAP или Framer Motion, но базовый цикл остаётся тем же: инициализация, обновление, рендер.
Подробное объяснение
Анимация шаг за шагом - это ручное управление каждым кадром, в отличие от CSS-анимаций или декларативных библиотек. Основной инструмент - requestAnimationFrame (rAF), который вызывает колбэк перед следующей отрисовкой браузера. Внутри колбэка:
- Вычисляется
deltaTime- разница во времени между текущим и предыдущим кадром. Это критично для плавности: анимация должна быть независима от FPS. - На основе
deltaTimeи длительности анимации рассчитывается прогресс (от 0 до 1). Можно использовать easing-функции для нелинейного движения. - Прогресс преобразуется в конкретные значения свойств: позиция, поворот, прозрачность.
- Эти значения применяются к элементу (через
style,transform,canvasAPI). - Если анимация не завершена, рекурсивно вызывается
rAF.
Важно: при паузе или переключении вкладок rAF автоматически приостанавливается, что экономит ресурсы. Для анимаций на canvas или WebGL подход тот же, но рендер идёт через контекст.
На практике
Начинаем с определения начального и конечного состояния, длительности и easing. Затем пишем функцию animate, которая принимает эти параметры и возвращает промис для отслеживания завершения. Внутри используем performance.now() для точного времени, а не Date.now(). Для отмены анимации сохраняем ID из requestAnimationFrame и вызываем cancelAnimationFrame.
Типичный сценарий: анимация появления модального окна (fade in + slide up). В каждом кадре обновляем opacity и transform: translateY. Для сложных цепочек анимаций (sequence) можно создать очередь или использовать async/await с промисами.
Пример кода
JAVASCRIPTfunction 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), // easeOutQuadonUpdate: 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 и %)
> Похожие задачи по frontend
Что такое лексическое окружение в JavaScript и как оно работает
Что такое Redux, зачем он нужен и как устроен
Реализовать функцию, принимающую массив промисов и возвращающую массив результатов или первый reject в JavaScript
Как реализовать мемоизацию функции в JavaScript
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью