Хуки для React

В React-обёртке Motion One анимации строятся вокруг идеи разделения императивного управления анимацией и реактивного жизненного цикла компонентов. Вместо того чтобы описывать анимации декларативно через сложные конфигурации, хуки дают прямой доступ к движку анимации, позволяя управлять DOM-элементами, значениями и событиями прокрутки с высокой точностью.

Ключевая особенность подхода — минимальная прослойка между React и нативным движком анимации. Это обеспечивает предсказуемую производительность и отсутствие лишних перерендеров при изменении анимационных значений.


useAnimate: императивная анимация элементов

Базовый хук для работы с анимациями — useAnimate. Он предоставляет функцию запуска анимации и ссылку на DOM-узел.

Основная идея заключается в том, что анимация вызывается как побочный эффект, а не как часть рендера.

import { useAnimate } from "@motionone/react";
import { useEffect } from "react";

function Box() {
  const [scope, animate] = useAnimate();

  useEffect(() => {
    animate(
      scope.current,
      { transform: "translateX(200px)", opacity: 0.5 },
      { duration: 0.8, easing: "ease-in-out" }
    );
  }, []);

  return <div ref={scope} className="box" />;
}

Особенности useAnimate

  • scope — ref-объект, привязанный к DOM-элементу
  • animate — функция запуска анимации
  • анимации выполняются вне цикла рендера React
  • поддерживается цепочка анимаций

Цепочки позволяют последовательно запускать несколько переходов:

useEffect(() => {
  animate(scope.current, { x: 100 });
  animate(scope.current, { opacity: 0 });
}, []);

В реальных сценариях такие вызовы часто объединяются через промисы или async-последовательность, чтобы контролировать порядок выполнения.


Управление жизненным циклом анимаций

Одной из ключевых проблем интеграции анимаций в React является синхронизация с монтажом и размонтированием компонентов. useAnimate автоматически привязывается к жизненному циклу через ref-ссылку.

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

  • активные анимации прерываются
  • очищаются таймеры
  • освобождаются ссылки на DOM

Это предотвращает утечки памяти и неконсистентное состояние интерфейса.


useScroll: реактивные анимации на основе прокрутки

useScroll позволяет привязывать анимационные значения к позиции скролла. Это основа для создания параллакса, прогресс-баров и динамических интерфейсов.

import { useScroll } from "@motionone/react";

function ScrollProgress() {
  const { scrollYProgress } = useScroll();

  scrollYProgress.on("change", (v) => {
    console.log(v);
  });

  return <div className="progress" />;
}

Модель данных scroll

Значение scrollYProgress представляет собой нормализованный диапазон:

  • 0 — начало прокрутки
  • 1 — конец страницы или контейнера

Это позволяет напрямую использовать значение для трансформаций:

const { scrollYProgress } = useScroll();

scrollYProgress.on("change", (v) => {
  document.body.style.setProperty("--progress", v);
});

Привязка scroll-контекста к элементу

Важный режим работы useScroll — отслеживание прокрутки конкретного контейнера.

const ref = useRef(null);
const { scrollYProgress } = useScroll({
  target: ref
});

Такой подход позволяет:

  • отслеживать скролл внутри блоков
  • создавать локальные анимационные сцены
  • избегать зависимости от окна браузера

Motion values и реактивные состояния

Motion One использует концепцию motion values — реактивных значений, которые обновляются без ререндеринга React-компонента.

Типичный сценарий:

import { useMotionValue } from "@motionone/react";

function Example() {
  const x = useMotionValue(0);

  x.on("change", (latest) => {
    console.log(latest);
  });

  return <div />;
}

Ключевые свойства motion values

  • не вызывают re-render React
  • обновляются напрямую в анимационном цикле
  • могут связываться между собой
  • поддерживают подписки на изменения

Это делает их аналогом observable-подхода в реактивном программировании.


useSpring: сглаживание значений

Для создания инерционных эффектов используется useSpring. Он преобразует резкие изменения значения в плавное движение.

import { useSpring, useMotionValue } from "@motionone/react";

function SpringBox() {
  const x = useMotionValue(0);
  const springX = useSpring(x, { stiffness: 120, damping: 20 });

  return <div />;
}

Параметры физической модели

  • stiffness — жесткость пружины
  • damping — коэффициент затухания
  • mass — масса объекта (в некоторых реализациях)

Такая модель позволяет имитировать физическое движение без ручного расчета интерполяции.


Связка useScroll и useSpring

Один из наиболее распространённых паттернов — сглаживание прокрутки через spring:

const { scrollY } = useScroll();
const smoothScroll = useSpring(scrollY, {
  stiffness: 100,
  damping: 30
});

Далее smoothScroll может использоваться для:

  • параллакс-эффектов
  • смещения элементов интерфейса
  • динамических масок и прозрачности

Композиция анимационных значений

Motion One позволяет комбинировать motion values в вычисляемые зависимости:

const x = useMotionValue(100);
const y = useMotionValue(50);

const transform = useTransform(
  [x, y],
  ([latestX, latestY]) => `translate(${latestX}px, ${latestY}px)`
);

Такой подход создаёт реактивную цепочку, где изменения одного значения автоматически пересчитывают производное.


useTransform: вычисляемые анимации

useTransform используется для преобразования входных значений в новые диапазоны.

Пример масштабирования:

const scale = useTransform(scrollYProgress, [0, 1], [1, 1.5]);

Здесь происходит линейное отображение:

  • 0 → 1
  • 1 → 1.5

Это удобно для:

  • анимации появления
  • изменения размеров интерфейса
  • управления прозрачностью

Оптимизация производительности хуков

Основная особенность Motion One — отсутствие необходимости перерендеров при каждом кадре анимации. Это достигается через:

  • прямое изменение DOM
  • использование requestAnimationFrame внутри движка
  • разделение React-состояния и анимационного состояния

Практическое следствие:

  • анимации не перегружают reconciler React
  • масштабирование интерфейсов остаётся стабильным даже при множестве объектов
  • прокрутка и анимации не конфликтуют с state-обновлениями

Привязка анимаций к событиям

Хуки Motion One могут использоваться не только для автоматических эффектов, но и для управления через события:

function Button() {
  const [scope, animate] = useAnimate();

  const handleClick = () => {
    animate(scope.current, {
      scale: [1, 1.2, 1]
    }, {
      duration: 0.4
    });
  };

  return <button ref={scope} onCl ick={handleClick} />;
}

Такая модель позволяет создавать сложные интерактивные микроанимации без сторонних стейт-менеджеров.


Синхронизация нескольких хуков

В сложных интерфейсах часто используется комбинация:

  • useScroll
  • useSpring
  • useTransform
  • useAnimate

Пример связки:

const { scrollYProgress } = useScroll();
const scale = useSpring(
  useTransform(scrollYProgress, [0, 1], [0.8, 1.2])
);

useEffect(() => {
  scale.on("change", (v) => {
    scope.current.style.transform = `scale(${v})`;
  });
}, []);

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


Управление несколькими элементами через scope

useAnimate поддерживает работу не только с одним элементом, но и с группами внутри одного scope:

const [scope, animate] = useAnimate();

useEffect(() => {
  animate(scope.current.querySelectorAll(".item"), {
    opacity: 1,
    y: 0
  });
}, []);

Это упрощает:

  • появление списков
  • stagger-анимации
  • последовательные переходы интерфейса

Stagger-анимации через хуки

Распространённый паттерн — каскадное появление элементов:

animate(
  ".item",
  { opacity: 1, y: 0 },
  { delay: stagger(0.1) }
);

Хотя stagger является частью API анимации, в React-хуках он часто применяется внутри useEffect, чтобы синхронизировать появление элементов после монтирования.


Разделение ответственности в архитектуре хуков

Внутренняя модель Motion One в React опирается на три уровня:

  • DOM-уровень — прямое управление стилями
  • motion values — реактивный слой состояния
  • React layer — жизненный цикл компонентов

Такое разделение позволяет:

  • избегать лишних рендеров
  • изолировать анимации от бизнес-логики
  • сохранять предсказуемость интерфейса

Ограничения и поведенческие особенности

Использование хуков накладывает ряд архитектурных особенностей:

  • анимации не должны зависеть от React state в реальном времени
  • частые пересоздания motion values могут приводить к лишним подпискам
  • необходимо аккуратно управлять эффектами, чтобы не дублировать анимации при повторных рендерах

Эти ограничения вытекают из асинхронной природы анимационного цикла и модели подписок.