Использование с React

Интеграция Velocity.js в компонентную модель React строится вокруг принципа разделения ответственности: React управляет состоянием и структурой DOM, Velocity.js отвечает за императивные анимации DOM-узлов, не вмешиваясь в виртуальное дерево напрямую.

Основная сложность использования Velocity.js внутри React заключается в том, что React обновляет DOM декларативно, тогда как Velocity.js работает императивно, напрямую модифицируя свойства элементов. Это создаёт необходимость строгого контроля моментов, когда элемент гарантированно присутствует в DOM.

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

Использование refs для доступа к DOM-элементам

React предоставляет механизм ссылок на DOM через refs, который становится основным мостом между компонентами и Velocity.js.

import React, { useRef, useEffect } from "react";
import Velocity from "velocity-animate";

const Box = () => {
  const boxRef = useRef(null);

  useEffect(() => {
    if (boxRef.current) {
      Velocity(boxRef.current, { opacity: 1, translateY: 0 }, { duration: 600 });
    }
  }, []);

  return <div ref={boxRef} style={{ opacity: 0, transform: "translateY(20px)" }} />;
};

В данном случае ref обеспечивает доступ к реальному DOM-элементу, а useEffect гарантирует выполнение анимации после монтирования.

Синхронизация с жизненным циклом компонентов

Анимации должны быть синхронизированы с этапами монтирования, обновления и размонтирования. Наиболее типовой паттерн — использование useEffect с зависимостями.

useEffect(() => {
  if (!boxRef.current) return;

  Velocity(boxRef.current, "stop");

  Velocity(boxRef.current, { opacity: isVisible ? 1 : 0 }, { duration: 300 });
}, [isVisible]);

Прерывание предыдущих анимаций через "stop" предотвращает накопление очередей анимаций при частых обновлениях состояния.

Управление входными и выходными анимациями

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

const Modal = ({ open }) => {
  const modalRef = useRef(null);

  useEffect(() => {
    if (!modalRef.current) return;

    if (open) {
      Velocity(modalRef.current, "stop");
      Velocity(modalRef.current, { opacity: 1, scale: 1 }, { duration: 250 });
    } else {
      Velocity(modalRef.current, { opacity: 0, scale: 0.9 }, { duration: 200 });
    }
  }, [open]);

  return open || modalRef.current ? (
    <div ref={modalRef} style={{ opacity: 0, transform: "scale(0.9)" }} />
  ) : null;
};

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

Хуки-обёртки для повторного использования анимаций

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

import { useEffect } from "react";
import Velocity from "velocity-animate";

const useVelocity = (ref, properties, deps = []) => {
  useEffect(() => {
    if (!ref.current) return;

    Velocity(ref.current, "stop");
    Velocity(ref.current, properties, { duration: 400 });
  }, deps);
};

Использование такого хука позволяет централизовать управление параметрами анимации и поведением остановки предыдущих эффектов.

Последовательные анимации и очереди

Velocity.js поддерживает очереди анимаций, которые в React должны использоваться осторожно из-за возможных повторных рендеров.

useEffect(() => {
  if (!ref.current) return;

  Velocity(ref.current, { translateY: -20 }, { duration: 200 })
    .then(() => Velocity(ref.current, { translateY: 0 }, { duration: 200 }));
}, []);

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

Работа с обновлениями состояния

Изменение состояния React может приводить к повторному применению анимаций, если зависимости useEffect заданы некорректно. Контроль выполняется через точечное указание зависимостей и проверку текущих значений.

useEffect(() => {
  if (!ref.current) return;

  const color = active ? "#4caf50" : "#f44336";

  Velocity(ref.current, "stop");
  Velocity(ref.current, { backgroundColor: color }, { duration: 300 });
}, [active]);

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

Производительность и минимизация перерасчётов

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

Снижение нагрузки достигается через:

  • остановку предыдущих анимаций перед запуском новых
  • избегание анимации свойств, вызывающих layout thrashing
  • использование transform и opacity как основных анимируемых свойств
  • ограничение частоты обновлений состояния

React-рендеры и анимации должны быть логически разделены: состояние управляет тем, что должно измениться, Velocity.js — как это визуально происходит.

Интеграция с условным рендерингом

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

const [render, setRender] = useState(true);

useEffect(() => {
  if (open) {
    setRender(true);
  } else if (ref.current) {
    Velocity(ref.current, { opacity: 0 }, {
      duration: 200,
      complete: () => setRender(false)
    });
  }
}, [open]);

Такой механизм позволяет синхронизировать визуальное исчезновение с фактическим удалением компонента.

SSR и гидратация

При использовании серверного рендеринга важно учитывать отсутствие DOM на этапе выполнения на сервере. Любые обращения к Velocity.js должны происходить только после монтирования компонента.

Проверка наличия window или использование useEffect гарантирует отсутствие ошибок гидратации. Анимации инициализируются только на клиенте, после того как React завершает сопоставление DOM.

Композиция сложных интерфейсов

В интерфейсах с большим количеством анимированных компонентов возникает необходимость координации нескольких Velocity.js вызовов. Для этого используется централизованное управление через родительские компоненты или контекст состояния.

Анимационные состояния синхронизируются с бизнес-логикой приложения, где React выступает источником истины, а Velocity.js — слоем визуализации изменений.