javascript

Плавность как фича: сравниваем фреймворки по анимации UI на реальных кейсах

  • вторник, 15 апреля 2025 г. в 00:00:03
https://habr.com/ru/articles/900664/

UI-анимации — это не только про красоту, но и про восприятие, структуру и даже скорость. В этой статье рассматриваются популярные фреймворки для создания анимаций в интерфейсах: CSS, Framer Motion, GSAP и Motion One. Сравнение проводится на реальных кейсах с кодом, примерами и субъективным мнением, где каждый инструмент показывает свои сильные и слабые стороны. В конце — небольшие выводы и неожиданные результаты.

Когда "просто появляется" уже не работает

Пару лет назад дизайнер принес макет с комментарием: «Вот тут кнопка должна мягко появляться, как облако, а потом улетать, как мыльный пузырь». Я кивнул, но подумал: «Это тебе не After Effects». Сегодня такие вещи вполне достижимы прямо в браузере — и с минимальными потерями по производительности. Вопрос в другом: чем именно это делать?

Чтобы разобраться, я взял 4 инструмента, с которыми чаще всего сталкивался в боевых и личных проектах:

  • CSS-анимации

  • GSAP

  • Framer Motion (для React)

  • Motion One (от создателей Framer Motion, но в Vanilla JS)

И сравнил их на трех простых, но показательных кейсах:

  1. Анимация появления элемента

  2. Анимация наведения

  3. Анимация между состояниями (shared element transition)

Кейсы и код

Кейc 1. Плавное появление карточки

CSS (чистый подход)

.card {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.4s ease, transform 0.4s ease;
}

.card.visible {
  opacity: 1;
  transform: translateY(0);
}
<div class="card">Контент</div>

<script>
  document.querySelector('.card').classList.add('visible');
</script>

GSAP

import gsap from "gsap";

gsap.fromTo(".card", 
  { opacity: 0, y: 20 }, 
  { opacity: 1, y: 0, duration: 0.4, ease: "power2.out" }
);

Framer Motion (React)

<motion.div
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{ duration: 0.4, ease: "easeOut" }}
>
  Контент
</motion.div>

Motion One

import { animate } from "motion";

animate(".card", { opacity: [0, 1], transform: ["translateY(20px)", "translateY(0)"] }, {
  duration: 0.4,
  easing: "ease-out"
});

Вывод:
CSS — просто и быстро, но ограничено. GSAP — супергибкий, особенно когда нужна последовательность анимаций. Framer Motion — прекрасно работает в React, почти магия. Motion One — гибкий и легкий, но ещё не так широко поддержан.

Кейc 2. Hover-анимации: реакция без тормозов

CSS (старый-добрый способ)

.button {
  transition: transform 0.3s ease;
}
.button:hover {
  transform: scale(1.05);
}

Framer Motion

<motion.button
  whileHover={{ scale: 1.05 }}
  transition={{ type: "spring", stiffness: 300 }}
>
  Нажми меня
</motion.button>

GSAP с событиями

const btn = document.querySelector('.button');

btn.addEventListener("mouseenter", () => {
  gsap.to(btn, { scale: 1.05, duration: 0.3 });
});

btn.addEventListener("mouseleave", () => {
  gsap.to(btn, { scale: 1, duration: 0.3 });
});

Комментарий:
CSS — самый производительный, потому что GPU делает всё сам. Но когда нужна упругая, резиновая, «живущая» анимация — тут лучше справляются GSAP или Framer Motion. Особенно Framer: hover-анимации с "spring" — кайф.

Кейc 3. Shared element transition: переход между состояниями

Это прям боль, особенно если нужно, чтобы элемент "переезжал" из одного экрана в другой. Типичная история в e-commerce: кликаешь на товар, карточка "перелетает" в детальную страницу — не исчезает и появляется заново, а плавно трансформируется.

Framer Motion (Layout animations в React)

На главной странице с карточками товаров:

<Link to={`/product/${id}`}>
  <motion.div layoutId={`product-image-${id}`} className="product-card">
    <img src="item.jpg" alt="item" />
    <p>Супертовар</p>
  </motion.div>
</Link>

На детальной странице:

<motion.div layoutId={`product-image-${id}`} className="product-detail">
  <img src="item.jpg" alt="item" />
  <h2>Супертовар</h2>
  <p>Описание товара, характеристики и прочее</p>
</motion.div>

Ключевой момент: layoutId должен совпадать. Тогда Framer Motion сам вычисляет позицию и размер обоих элементов, и делает плавный переход между ними при навигации.

GSAP Flip Plugin (для Vanilla JS или любой библиотеки)

<div class="product-card" data-id="123">
  <img src="item.jpg" />
</div>
import { Flip } from "gsap/Flip";

const card = document.querySelector(".product-card");
const detail = document.querySelector(".product-detail");

const state = Flip.getState(card);

detail.appendChild(card); // перемещаем DOM-элемент

Flip.from(state, {
  duration: 1,
  ease: "power1.inOut"
});

Комментарий:
Фреймер — магия без боли. Но работает только в пределах React и требует чуть понимания AnimatePresence. GSAP с Flip — пушка, но требует настройки и оплаты. CSS здесь, честно говоря, просто не справляется.

Маленькие неожиданности

  • GSAP идеально подходит для длинных цепочек и сложных последовательных анимаций. Особенно когда всё строится динамически.

  • Framer Motion ощущается как нативный язык анимаций в React.

  • Motion One — легковесная альтернатива, которая приятно удивляет.

  • CSS — быстрый и надёжный, но ограниченный.

А что по производительности?

Я сделал несколько замеров через Chrome DevTools и увидел, что...

  • CSS и Motion One — лидеры по FPS и плавности.

  • Framer Motion — чуть тяжелее, но приемлемо.

  • GSAP — самый тяжелый, особенно при множестве элементов, но и самый мощный.

Итого

Универсального ответа нет. Всё зависит от задачи:

  • Нужно просто и быстро? CSS

  • Анимация в React-приложении? Framer Motion

  • Сложные переходы и динамика? GSAP

  • Минимализм и гибкость? Motion One

Если вы делаете сложные интерфейсы, обязательно посмотрите, что происходит под капотом. Плавность — это не «вау», это структурный элемент восприятия. И она не должна быть случайной.

Хочешь примеры этих кейсов в CodeSandbox или Stackblitz? Пиши в комментариях — с радостью поделюсь.