> Как реализовать анимацию блока с подпрыгиванием, изменением цвета и улетом за пределы экрана (JavaScript)

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

Компании: MTS

Стек: JavaScript

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

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

Анимация блока с подпрыгиванием, изменением цвета и улётом за экран реализуется через комбинацию CSS-переходов и keyframe-анимаций, управляемых JavaScript-состоянием. Оптимальный подход - использовать Web Animations API или CSS-анимации с animationend для последовательного запуска этапов. Ключевой момент - разделение логики на фазы: bounce → color shift → fly out, с явным управлением таймингом и завершением через requestAnimationFrame или промисы.

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

Задача состоит из трёх последовательных фаз, каждая из которых требует своего подхода:

  1. Подпрыгивание - циклическая анимация с затуханием амплитуды. Реализуется через keyframes с несколькими bounce-точками (например, 0%, 20%, 40%, 60%, 80%, 100%), где высота прыжка уменьшается. Можно использовать CSS animation с ease-out на каждом bounce.

  2. Изменение цвета - либо плавный переход через transition на background-color, либо отдельная keyframe-анимация. Для плавности лучше использовать transition с длительностью 300-500ms, но в контексте последовательности удобнее keyframes.

  3. Улёт за экран - трансформация translate с большим значением (например, translateY(-200vh) или translateX(100vw)), часто с вращением для эффектности. Здесь важно учитывать will-change: transform для производительности.

Управление последовательностью - ключевая сложность. Варианты:

  • CSS-only: через animation-delay на каждом этапе, но это хрупко и сложно синхронизировать с состоянием.
  • JavaScript + события: слушать animationend и запускать следующую фазу. Надёжно, но требует аккуратной очистки слушателей.
  • Web Animations API: element.animate() возвращает Animation объект с промисом finished - это самый чистый способ для последовательных анимаций.
  • Промисы + requestAnimationFrame: полный контроль, но больше кода.

Для senior-уровня важно обсудить trade-off между подходами: производительность (композитинг transform/opacity), доступность (prefers-reduced-motion), отмену анимации при размонтировании компонента.

На практике

На практике я бы выбрал Web Animations API, потому что он даёт:

  • нативный промис finished для композиции анимаций;
  • возможность отмены через animation.cancel();
  • контроль над fill mode (forwards для удержания конечного состояния);
  • отсутствие необходимости в дополнительных библиотеках.

Альтернатива - CSS-классы с animationend, но это требует ручного управления слушателями и сложнее в отмене.

Важный нюанс: для улёта за экран использовать translate с viewport-единицами, а не top/left, чтобы не вызывать reflow. Также стоит добавить will-change: transform на время анимации и убрать после.

Пример кода

JAVASCRIPT
async function animateBlock(el) {
// Фаза 1: подпрыгивание
const bounce = el.animate(
[
{ transform: 'translateY(0)' },
{ transform: 'translateY(-80px)', offset: 0.2 },
{ transform: 'translateY(0)', offset: 0.4 },
{ transform: 'translateY(-40px)', offset: 0.6 },
{ transform: 'translateY(0)', offset: 0.8 },
{ transform: 'translateY(-10px)', offset: 0.9 },
{ transform: 'translateY(0)' }
],
{ duration: 800, easing: 'ease-out' }
);
await bounce.finished;
// Фаза 2: изменение цвета
const color = el.animate(
[{ backgroundColor: '#3498db' }, { backgroundColor: '#e74c3c' }],
{ duration: 400, fill: 'forwards' }
);
await color.finished;
// Фаза 3: улёт за экран
const flyOut = el.animate(
[
{ transform: 'translateY(0) rotate(0deg)', opacity: 1 },
{ transform: 'translateY(-120vh) rotate(720deg)', opacity: 0.5 }
],
{ duration: 700, easing: 'cubic-bezier(0.55, 0, 0.55, 0.2)', fill: 'forwards' }
);
await flyOut.finished;
}
// Использование
const block = document.querySelector('.block');
block.addEventListener('click', () => animateBlock(block));

CSS для базового состояния:

CSS
.block {
width: 100px;
height: 100px;
background: #3498db;
border-radius: 8px;
will-change: transform;
}

Для отмены при размонтировании:

JAVASCRIPT
let currentAnimation = null;
async function animateBlock(el) {
currentAnimation?.cancel();
// ... та же логика, но сохраняем ссылки
}

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

Начни с декомпозиции задачи на три фазы и сразу предложи Web Animations API как основной инструмент. Объясни, почему не CSS-only: сложность синхронизации и отмены. Упомяни про animation.finished как промис - это показывает понимание современных API.

Затем перейди к деталям реализации: keyframes для bounce с затуханием, fill: 'forwards' для удержания цвета, использование viewport-единиц для улёта. Добавь про производительность: transform/opacity не вызывают reflow, will-change нужно убирать после завершения.

В конце упомяни edge cases: prefers-reduced-motion, отмена при размонтировании, обработка ошибок через try/catch вокруг finished.

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

  • Понимание разницы между CSS-анимациями и Web Animations API.
  • Умение композировать последовательные анимации без гонок состояний.
  • Знание про производительность: compositor-friendly свойства.
  • Внимание к деталям: fill mode, отмена, доступность.
  • Способность предложить несколько подходов и обосновать выбор.

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

  • Использование setTimeout для синхронизации - хрупко и не учитывает реальную длительность анимации.
  • Забывают про fill: 'forwards' - блок возвращается в исходное состояние после анимации.
  • Анимация top/left вместо transform - вызывает reflow и лаги.
  • Не обрабатывают отмену анимации при повторном клике или размонтировании.
  • Игнорируют prefers-reduced-motion - для пользователей с vestibular disorders это критично.
  • Пытаются сделать всё одной анимацией с огромным количеством keyframes - сложно поддерживать.

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

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