Интеграция Velocity.js в компонентную модель React строится вокруг принципа разделения ответственности: React управляет состоянием и структурой DOM, Velocity.js отвечает за императивные анимации DOM-узлов, не вмешиваясь в виртуальное дерево напрямую.
Основная сложность использования Velocity.js внутри React заключается в том, что React обновляет DOM декларативно, тогда как Velocity.js работает императивно, напрямую модифицируя свойства элементов. Это создаёт необходимость строгого контроля моментов, когда элемент гарантированно присутствует в DOM.
Ключевой механизм — привязка анимаций к жизненному циклу компонентов. 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. Основной принцип — группировка изменений и отказ от частых мелких анимаций.
Снижение нагрузки достигается через:
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]);
Такой механизм позволяет синхронизировать визуальное исчезновение с фактическим удалением компонента.
При использовании серверного рендеринга важно учитывать отсутствие DOM на этапе выполнения на сервере. Любые обращения к Velocity.js должны происходить только после монтирования компонента.
Проверка наличия window или использование useEffect
гарантирует отсутствие ошибок гидратации. Анимации инициализируются
только на клиенте, после того как React завершает сопоставление DOM.
В интерфейсах с большим количеством анимированных компонентов возникает необходимость координации нескольких Velocity.js вызовов. Для этого используется централизованное управление через родительские компоненты или контекст состояния.
Анимационные состояния синхронизируются с бизнес-логикой приложения, где React выступает источником истины, а Velocity.js — слоем визуализации изменений.