Анимация в интерфейсе — это как соль в супе. Без неё пресно, а если переборщить — есть невозможно. Пользователи любят, когда элементы плавно появляются, кнопки отзываются на нажатие, а модальные окна не выскакивают, как чёрт из табакерки. Но за красивыми эффектами стоит код, и если его написать неправильно, сайт начнёт тормозить даже на мощных машинах.

В этой статье я расскажу, как делать анимации на JavaScript, которые не бесят, а радуют. Мы пройдём путь от простых CSS-переходов до Web Animations API, разберём типичные ошибки и научимся измерять производительность. Код будет с примерами, так что сможете сразу пробовать.

Digital-студия WNDER

Почему CSS-анимации — это не всегда серебряная пуля

Когда речь заходит об анимациях, первое, что приходит в голову — CSS. И это логично: браузер умеет оптимизировать CSS-переходы и анимации, вынося их в отдельный поток. Но у CSS есть ограничения: вы не можете динамически менять параметры на лету, останавливать анимацию на середине или синхронизировать несколько эффектов. Тут на сцену выходит JavaScript.

Например, вам нужно, чтобы элемент двигался по кривой Безье, зависящей от положения курсора. Или чтобы анимация запускалась после того, как пользователь прокрутил до определённого места. CSS такое не умеет — нужен JS.

Но не спешите хвататься за requestAnimationFrame. Сначала проверьте, можно ли решить задачу через CSS-переходы с изменением классов. Это проще и производительнее.

// Плохо: анимация через JS с изменением left
const box = document.querySelector('.box');
let position = 0;
function animate() {
  position += 1;
  box.style.left = position + 'px';
  if (position < 300) requestAnimationFrame(animate);
}
animate();

// Хорошо: используем CSS transition и класс
// CSS: .box { transition: transform 0.3s; }
// .box.moved { transform: translateX(300px); }
box.classList.add('moved');

Во втором случае браузер сам оптимизирует анимацию, а вы не грузите главный поток.

requestAnimationFrame: ваш главный инструмент

Если без JS не обойтись, используйте requestAnimationFrame (сокращённо rAF). Это метод, который говорит браузеру: «Выполни мою функцию перед следующей перерисовкой экрана». Он синхронизирует анимацию с частотой обновления монитора (обычно 60 кадров в секунду).

Вот простой пример плавного изменения прозрачности:

function fadeIn(element, duration = 1000) {
  let start = null;
  const from = 0;
  const to = 1;

  function step(timestamp) {
    if (!start) start = timestamp;
    const progress = (timestamp - start) / duration;
    element.style.opacity = Math.min(progress, 1);
    if (progress < 1) {
      requestAnimationFrame(step);
    }
  }

  requestAnimationFrame(step);
}

fadeIn(document.querySelector('.modal'));

Обратите внимание: мы используем timestamp, который передаёт rAF, чтобы вычислить прогресс. Это надёжнее, чем считать кадры вручную.

Правило: Никогда не используйте setInterval или setTimeout для анимаций. Они не синхронизированы с перерисовкой и могут вызывать дёргание. Только requestAnimationFrame.

Web Animations API: мощь CSS и гибкость JS

Web Animations API (WAAPI) — это относительно новый стандарт, который позволяет управлять анимациями из JavaScript, но с производительностью CSS. Вы описываете ключевые кадры и параметры, а браузер делает всё сам.

Пример анимации поворота:

const element = document.querySelector('.spinner');
const animation = element.animate([
  { transform: 'rotate(0deg)' },
  { transform: 'rotate(360deg)' }
], {
  duration: 2000,
  iterations: Infinity,
  easing: 'linear'
});

// Можно поставить на паузу
animation.pause();

// Или изменить скорость
animation.playbackRate = 2;

WAAPI поддерживается всеми современными браузерами, кроме IE. Если нужна поддержка старичков, используйте полифилл.

Главный плюс WAAPI — вы получаете объект анимации, которым можно управлять: останавливать, перезапускать, менять направление. И всё это без ручного пересчёта кадров.

Сравнение подходов к анимации

Чтобы выбрать инструмент, давайте сравним основные способы.

Подход Производительность Гибкость Сложность
CSS transitions Высокая Низкая Низкая
requestAnimationFrame Средняя Высокая Средняя
Web Animations API Высокая Высокая Средняя
Библиотеки (GSAP, Anime.js) Высокая Очень высокая Низкая

Как видите, для простых задач хватит CSS, для сложных — WAAPI или библиотеки. rAF — золотая середина, когда нужен полный контроль.

Лайфхаки для плавных анимаций

Даже с правильным инструментом можно получить тормоза. Вот несколько советов, которые сэкономят вам нервы.

  • Анимируйте только transform и opacity. Эти свойства не вызывают перерасчёт геометрии (reflow) и перерисовку (repaint). Всё остальное — width, height, left, background-color — дорого.
  • Используйте will-change. Если элемент точно будет анимироваться, добавьте will-change: transform. Браузер заранее выделит слой. Но не злоупотребляйте — это жрёт память.
  • Следите за частотой кадров. Откройте DevTools, вкладку Performance, запишите анимацию и посмотрите, не падает ли FPS ниже 60.
  • Не анимируйте всё сразу. Если у вас 100 элементов, каждый со своей анимацией, — беда. Используйте делегирование или анимируйте контейнер.
Лайфхак: Для отладки анимаций используйте Chrome DevTools → More tools → Animations. Там можно замедлить анимацию, посмотреть тайминги и даже редактировать ключевые кадры.

Пример: анимация появления карточек при скролле

Давайте сделаем что-то практичное. Например, карточки товаров, которые плавно выезжают снизу, когда пользователь доскроллил до них. Это классика.

HTML:

<div class="cards">
  <div class="card">Карточка 1</div>
  <div class="card">Карточка 2</div>
  <div class="card">Карточка 3</div>
</div>

CSS:

.card {
  opacity: 0;
  transform: translateY(50px);
  transition: opacity 0.6s, transform 0.6s;
}
.card.visible {
  opacity: 1;
  transform: translateY(0);
}

JavaScript с Intersection Observer:

const cards = document.querySelectorAll('.card');

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      entry.target.classList.add('visible');
      observer.unobserve(entry.target); // чтобы не срабатывало повторно
    }
  });
}, { threshold: 0.1 });

cards.forEach(card => observer.observe(card));

Этот подход эффективен: мы не слушаем скролл напрямую, а используем Intersection Observer, который работает асинхронно и не грузит главный поток.

Что в итоге

Анимация интерфейсов — это не магия, а инженерия. Главные правила: используйте CSS там, где можно, rAF — где нужно, и WAAPI — где хочется гибкости. Следите за производительностью, анимируйте только transform и opacity, и не забывайте про инструменты разработчика.

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

Экспериментируйте, измеряйте, оптимизируйте. И пусть ваши интерфейсы будут плавными, как шёлк.

Студия WNDER