Анимации в React с GSAP

30 октября 2026
Автор

Антон Ларичев

Что такое GSAP и зачем он нужен в React

GSAP (GreenSock Animation Platform) — это JavaScript-библиотека для создания высокопроизводительных анимаций. Она работает быстрее CSS-анимаций в сложных сценариях, поддерживает любые DOM-элементы, SVG, Canvas и даже произвольные числовые значения. В отличие от Framer Motion, GSAP — императивная библиотека: вы явно указываете, что и как анимировать.

GSAP хорошо вписывается в React, если понимать несколько ключевых правил:

  • Не трогайте DOM напрямую — используйте useRef для получения ссылки на элемент.
  • Запускайте анимации после монтирования — в useEffect или через хук useGSAP.
  • Всегда возвращайте cleanup-функцию — чтобы GSAP не создавал утечек памяти.

Курс по теме

Курс React и Redux Toolkit с нуля

40 000+ студентов · рейтинг 4.8 · гарантия возврата 30 дней

Установка

npm install gsap

Если нужен ScrollTrigger или другие плагины:

npm install gsap

Все основные плагины (ScrollTrigger, Draggable, MorphSVGPlugin и др.) поставляются в одном пакете. Для плагинов клубной лицензии (SplitText, MorphSVG) потребуется аккаунт GreenSock.

Базовая анимация через useRef и useEffect

Самый простой способ — получить ссылку на DOM-элемент через useRef и запустить анимацию в useEffect.

import { useRef, useEffect } from 'react';
import gsap from 'gsap';

export function FadeInBox() {
  const boxRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const ctx = gsap.context(() => {
      gsap.from(boxRef.current, {
        opacity: 0,
        y: 40,
        duration: 0.8,
        ease: 'power2.out',
      });
    }, boxRef);

    return () => ctx.revert();
  }, []);

  return (
    <div
      ref={boxRef}
      style={{ width: 100, height: 100, background: 'rebeccapurple' }}
    />
  );
}

gsap.context() — это встроенный механизм GSAP для групповой очистки. Всё, что создано внутри контекста, будет уничтожено при вызове ctx.revert(). Это эквивалентно ручному сохранению списка твинов и их остановке — но намного удобнее.

gsap.from анимирует элемент из указанных значений в его текущее состояние. Противоположный метод — gsap.to — анимирует в указанные значения. gsap.fromTo позволяет задать оба конца анимации явно.

Хук useGSAP

GSAP предоставляет официальный React-хук, который инкапсулирует весь шаблонный код с useEffect и gsap.context.

npm install @gsap/react
import { useRef } from 'react';
import gsap from 'gsap';
import { useGSAP } from '@gsap/react';

gsap.registerPlugin(useGSAP);

export function SlideInCard() {
  const containerRef = useRef<HTMLDivElement>(null);

  useGSAP(
    () => {
      gsap.from('.card', {
        x: -100,
        opacity: 0,
        duration: 0.6,
        stagger: 0.15,
        ease: 'back.out(1.7)',
      });
    },
    { scope: containerRef }
  );

  return (
    <div ref={containerRef}>
      <div className="card">Карточка 1</div>
      <div className="card">Карточка 2</div>
      <div className="card">Карточка 3</div>
    </div>
  );
}

Параметр scope ограничивает область поиска CSS-селекторов текущим контейнером. Без него '.card' найдёт все элементы с таким классом на странице.

stagger — это задержка между анимациями нескольких элементов. 0.15 означает, что каждая следующая карточка начнёт анимацию на 150 мс позже предыдущей.

useGSAP по умолчанию принимает те же зависимости, что и useEffect:

useGSAP(
  () => {
    gsap.to('.bar', { width: `${progress}%`, duration: 0.4 });
  },
  { scope: containerRef, dependencies: [progress] }
);

Таймлайны — последовательные и параллельные анимации

Для сложных многошаговых сценариев используют gsap.timeline(). Таймлайн позволяет выстраивать анимации в цепочку, добавлять метки и управлять всем как одним объектом.

import { useRef } from 'react';
import gsap from 'gsap';
import { useGSAP } from '@gsap/react';

gsap.registerPlugin(useGSAP);

export function HeroSection() {
  const containerRef = useRef<HTMLDivElement>(null);

  useGSAP(
    () => {
      const tl = gsap.timeline({ defaults: { ease: 'power3.out' } });

      tl.from('.hero-title', { y: 60, opacity: 0, duration: 0.7 })
        .from('.hero-subtitle', { y: 40, opacity: 0, duration: 0.5 }, '-=0.3')
        .from('.hero-button', { scale: 0.8, opacity: 0, duration: 0.4 }, '-=0.2');
    },
    { scope: containerRef }
  );

  return (
    <section ref={containerRef}>
      <h1 className="hero-title">Добро пожаловать</h1>
      <p className="hero-subtitle">Создавайте быстро и красиво</p>
      <button className="hero-button">Начать</button>
    </section>
  );
}

Значение '-=0.3' в позиции означает «начни на 0.3 секунды раньше предыдущей анимации». Это создаёт перекрытие анимаций, что выглядит более плавно, чем строгая последовательность.

defaults задаёт параметры, общие для всех твинов таймлайна — чтобы не повторять одно и то же ease в каждом вызове.

ScrollTrigger — анимации при прокрутке

ScrollTrigger — плагин GSAP, который привязывает анимации к позиции скролла.

import { useRef } from 'react';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import { useGSAP } from '@gsap/react';

gsap.registerPlugin(ScrollTrigger, useGSAP);

export function AnimatedSection() {
  const containerRef = useRef<HTMLDivElement>(null);

  useGSAP(
    () => {
      gsap.from('.feature-item', {
        y: 60,
        opacity: 0,
        duration: 0.7,
        stagger: 0.2,
        ease: 'power2.out',
        scrollTrigger: {
          trigger: '.feature-list',
          start: 'top 80%',
          end: 'bottom 20%',
          toggleActions: 'play none none reverse',
        },
      });
    },
    { scope: containerRef }
  );

  return (
    <div ref={containerRef}>
      <ul className="feature-list">
        <li className="feature-item">Высокая скорость</li>
        <li className="feature-item">Гибкая настройка</li>
        <li className="feature-item">Полная документация</li>
      </ul>
    </div>
  );
}

start: 'top 80%' означает: «начать, когда верхний край триггера достигнет 80% высоты viewport». toggleActions задаёт поведение для четырёх событий: onEnter, onLeave, onEnterBack, onLeaveBack.

Возможные значения для каждого события: play, pause, resume, reset, restart, complete, reverse, none.

Для параллакс-эффекта используют scrub:

useGSAP(
  () => {
    gsap.to('.parallax-bg', {
      yPercent: -30,
      ease: 'none',
      scrollTrigger: {
        trigger: '.parallax-section',
        start: 'top bottom',
        end: 'bottom top',
        scrub: true,
      },
    });
  },
  { scope: containerRef }
);

scrub: true привязывает прогресс анимации напрямую к позиции скролла. Число вместо true (например, scrub: 0.5) добавляет инерцию.

Анимация при наведении

Для интерактивных анимаций по событиям мыши GSAP используется в обработчиках событий. Важно создавать анимации внутри useGSAP или контролировать их жизненный цикл вручную.

import { useRef } from 'react';
import gsap from 'gsap';
import { useGSAP } from '@gsap/react';

gsap.registerPlugin(useGSAP);

export function HoverCard() {
  const cardRef = useRef<HTMLDivElement>(null);

  useGSAP(
    () => {
      const card = cardRef.current;
      if (!card) return;

      const enterAnim = gsap.to(card, {
        scale: 1.05,
        boxShadow: '0 20px 40px rgba(0,0,0,0.2)',
        duration: 0.3,
        ease: 'power2.out',
        paused: true,
      });

      card.addEventListener('mouseenter', () => enterAnim.play());
      card.addEventListener('mouseleave', () => enterAnim.reverse());
    },
    { scope: cardRef }
  );

  return (
    <div
      ref={cardRef}
      style={{
        padding: 24,
        background: '#fff',
        borderRadius: 12,
        cursor: 'pointer',
        userSelect: 'none',
      }}
    >
      Наведите на меня
    </div>
  );
}

Создание твина с paused: true и последующий вызов .play() / .reverse() — стандартный паттерн для двунаправленных hover-анимаций. Он эффективнее, чем создавать новый твин при каждом наведении.

Работа с SVG

GSAP отлично работает с SVG-элементами — можно анимировать stroke-dashoffset для эффекта рисования линий.

import { useRef } from 'react';
import gsap from 'gsap';
import { useGSAP } from '@gsap/react';

gsap.registerPlugin(useGSAP);

export function DrawingCircle() {
  const svgRef = useRef<SVGSVGElement>(null);

  useGSAP(
    () => {
      const circle = svgRef.current?.querySelector('circle');
      if (!circle) return;

      const length = circle.getTotalLength();

      gsap.set(circle, {
        strokeDasharray: length,
        strokeDashoffset: length,
      });

      gsap.to(circle, {
        strokeDashoffset: 0,
        duration: 1.5,
        ease: 'power2.inOut',
      });
    },
    { scope: svgRef }
  );

  return (
    <svg ref={svgRef} width="120" height="120" viewBox="0 0 120 120">
      <circle
        cx="60"
        cy="60"
        r="50"
        fill="none"
        stroke="rebeccapurple"
        strokeWidth="4"
      />
    </svg>
  );
}

Типичные ошибки и как их избежать

Запуск анимации без useEffect

Если вызвать gsap.to прямо в теле компонента, анимация будет запускаться при каждом рендере — это сломает поведение и создаст утечки.

// Неправильно
export function Broken() {
  const ref = useRef(null);
  gsap.to(ref.current, { x: 100 }); // запускается на каждом рендере
  return <div ref={ref} />;
}

// Правильно
export function Correct() {
  const ref = useRef(null);
  useGSAP(() => {
    gsap.to(ref.current, { x: 100 });
  }, { scope: ref });
  return <div ref={ref} />;
}

Забытая регистрация плагинов

GSAP-плагины нужно регистрировать один раз перед использованием. Лучше делать это на уровне модуля, а не внутри компонента.

// Один раз в верхней части файла или в отдельном gsap-config.ts
import { ScrollTrigger } from 'gsap/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);

Проблемы с React StrictMode

В режиме StrictMode React намеренно монтирует и размонтирует компоненты дважды в development. Именно поэтому важна очистка через ctx.revert() или через useGSAP — без неё анимации будут дублироваться.

Конфликт с React State

Не стоит хранить ссылки на твины в state — это вызовет лишние рендеры. Для хранения объектов GSAP используйте useRef.

const tlRef = useRef<gsap.core.Timeline | null>(null);

useGSAP(() => {
  tlRef.current = gsap.timeline().from('.item', { opacity: 0 });
});

// Позже можно управлять таймлайном
const handlePause = () => tlRef.current?.pause();

Интеграция с React Router

При навигации между страницами полезно делать анимации входа и выхода. Простой паттерн — оборачивать страницы в компонент с анимацией монтирования.

import { useRef } from 'react';
import { useGSAP } from '@gsap/react';
import gsap from 'gsap';

export function PageTransition({ children }: { children: React.ReactNode }) {
  const wrapperRef = useRef<HTMLDivElement>(null);

  useGSAP(
    () => {
      gsap.from(wrapperRef.current, {
        opacity: 0,
        y: 20,
        duration: 0.4,
        ease: 'power2.out',
      });
    },
    { scope: wrapperRef }
  );

  return <div ref={wrapperRef}>{children}</div>;
}

Оберните каждую страницу в этот компонент — и вы получите плавный fade-in при каждом переходе.

Итог

GSAP в React строится на трёх принципах: DOM-доступ через useRef, инициализация анимаций через useGSAP (или useEffect), очистка через ctx.revert(). Для последовательных сценариев используйте таймлайны, для анимаций при прокрутке — ScrollTrigger. Регистрируйте плагины один раз на уровне модуля и не храните GSAP-объекты в state.

Подробнее о разработке на React — в курсе на PurpleSchool: Курс по React на PurpleSchool

Стрелочка влевоRadix UI: компоненты без стилейReact forwardRef — передача рефов дочерним компонентамСтрелочка вправо

Постройте личный план изучения React до уровня Middle — бесплатно!

React — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по React

React тестирование с Vitest
Uncontrolled Components: когда DOM управляет даннымиZustand: продвинутые паттерны, persist middleware и slicesБезопасность в React: защита от XSS, CSRF и утечек данныхRender Props: гибкое управление рендерингом в ReactРефакторинг React-кода: техники и лучшие практикиReact с WebSocket — реалтайм в компонентахReact Native New Architecture: Fabric и JSIReact Native приложение с нуля до публикации в сторахПрофилирование React: как найти и устранить узкие местаЧастичное применение: как создавать компоненты без лишнего кодаИменование компонентов в React: соглашения и лучшие практикиЛенивая загрузка: как ускорить React-приложение в разыОшибки гидратации в React и способы их устраненияHOC в React: мастерство композиции компонентовАрхитектура больших React приложений: Feature-Sliced DesignuseMemo: как спасти производительность от тяжелых вычисленийError Boundaries: создаем надежные React-приложенияКонтролируемые компоненты в React: полный контроль над формамиCompound Components в React: создаем гибкие компоненты с мощным APIДокументирование компонентов в React: Storybook, JSDoc и READMEКомпозиция компонентов в React: строим гибкие интерфейсыКомментирование кода в React: когда и как писать комментарииCode Splitting в React: как уменьшить бандл и ускорить загрузку приложенияАсинхронные компоненты в React: новый стандарт работы с даннымиДоступность (a11y) в React: ARIA, семантика и клавиатурная навигация
Zustand — управление состоянием в ReactZod - валидация с TypeScriptYup - валидация схемXState - конечные автоматыТемизация в ReactТестирование хуковTailwind CSS с ReactSWR - библиотека для запросовStyled Components — стилизация через JSStorybook - документация компонентовSnapshots тестированиеRTK Query - работа с APIRedux Toolkit - современный ReduxRecoil — библиотека управления состоянием от FacebookВиртуализация списков с react-window: как отображать тысячи элементов без лаговReact Toastify - уведомления в ReactReact Testing LibraryСоздание таблиц в React гайд по react-tableReact Spring - анимацииРабота с формами и селектами в ReactReact Query (TanStack Query) - работа с серверомПлагины в React что это и как их использоватьReact PDF - работа с PDF файламиОбзор популярных библиотек для ReactReact Icons - библиотека иконок для ReactReact Hook Form — валидация форм в ReactReact Dropzone — загрузка файловПодключение Bootstrap к React-приложениюReact Beautiful DnD - перетаскивание элементовАнимация при монтировании компонентов в ReactМокирование APIMobX — реактивное управление состоянием в ReactМикрофронтенды с React (micro-frontends)Загрузка и индикаторыАнимация списков в ReactJotai - атомарное состояниеБесконечная прокруткаFramer Motion - библиотека анимацийEmotion — библиотека CSS-in-JSДинамические стили в ReactE2E тестирование с CypressCSSTransition - переходыCSS-in-JS — плюсы и минусыКонтекст vs Redux — когда что использоватьAxios с ReactИспользование Chart.js в ReactТестирование асинхронных компонентовОбработка ошибок API
Valtio: атомарный state management в ReactuseState в React что это и как использоватьuseTransition - плавные переходы между состояниямиuseSyncExternalStore — работа с внешними сторамиuseRef в React — создание ссылок на DOM и значенияuseOptimistic — оптимистичные обновления UIuseLayoutEffect в React — эффект до отрисовкиuseInsertionEffect — внедрение стилей до мутаций DOMuseImperativeHandle в React — настройка ref дочернего компонентаuseId — генерация уникальных идентификаторовuseFormStatus - отслеживание статуса отправки формыuseDeferredValue — отложенное обновление состоянияuseDebugValue — отладка кастомных хуковuseCallback в React — мемоизация функцийuseReducer — альтернатива useState для сложной логикиuseMemo в React: как и когда оптимизировать тяжелые вычисленияХук use() в React 19useEffect в React что это и как использоватьuseContext — работа с контекстом в ReactuseCallback в React — мемоизация функций и оптимизация ре-рендеровuseActionState в React 19React Stale Closures — устаревшие замыкания в хукахОптимизация рендеринга в React: от теории к глубокой практикеЧто такое useRef и как его применять в ReactКак и зачем использовать React HooksУправление состоянием в React через ContextНовые возможности React 19Как предотвратить лишние ре-рендеры в React: полное руководствоuseMemo vs useCallback: подробное руководство по мемоизации в ReactПравила хуков — правила использованияflushSync — синхронные обновления состояния в ReactuseEffect vs useLayoutEffect: в чём разница и какой хук выбрать?Кастомные хуки в React — создание собственных хуковuseState продвинутое использование в React
Transition API — плавные обновления интерфейса в ReactReact Suspense — приостановка рендераStrictMode в React — как находить ошибки на этапе разработкиСерверные компоненты React (RSC) — подробный разбор и практикаКак работает рендеринг в ReactЧто такое props в React и как их правильно использоватьЧто такое React.js и как его использоватьКак работает JSX связка React и HTMLКак использовать элементы в ReactКак использовать React DOM в проектеЧто такое компоненты в React и как их применятьРабота с children в ReactПорталы в React: рендер компонентов вне иерархии DOMFragment в React: группировка элементов без лишних узлов DOMCSS Modules в ReactConcurrent Mode — конкурентный режим в React
Открыть базу знаний

Лучшие курсы по теме

изображение курса

React и Redux Toolkit

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий