Переиспользуемые анимации

Переиспользуемые анимации в Velocity.js строятся вокруг идеи отделения описания поведения анимации от конкретного DOM-элемента и момента её вызова. Такой подход устраняет дублирование, упрощает поддержку интерфейса и позволяет масштабировать анимационную систему без роста связности между компонентами.

Основная проблема прямого использования анимаций заключается в повторении одинаковых наборов параметров:

Velocity(element, {
  opacity: 1,
  translateY: 0
}, {
  duration: 300,
  easing: "easeOutQuad"
});

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

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

Базовый шаблон анимации

Наиболее простой способ организации переиспользуемых анимаций — создание функций-обёрток.

const fadeInUp = (element) => {
  return Velocity(element, {
    opacity: 1,
    translateY: 0
  }, {
    duration: 300,
    easing: "easeOutQuad"
  });
};

Такая функция инкапсулирует поведение и превращает анимацию в переиспользуемый блок логики. Вся настройка скрыта внутри функции, интерфейс использования становится минимальным.

Параметризация анимаций

Жёстко зашитые значения ограничивают гибкость. Следующий уровень абстракции — параметризация.

const fadeInUp = (element, options = {}) => {
  const {
    duration = 300,
    distance = 20,
    easing = "easeOutQuad"
  } = options;

  return Velocity(element, {
    opacity: 1,
    translateY: 0
  }, {
    duration,
    easing,
    begin: () => {
      element.style.transform = `translateY(${distance}px)`;
    }
  });
};

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

Фабрики анимаций

Когда количество анимаций растёт, функции-обёртки становятся громоздкими. Более гибкий подход — фабрики, возвращающие настроенные анимационные функции.

const createFadeAnimation = (config = {}) => {
  const {
    duration = 300,
    easing = "easeOutQuad"
  } = config;

  return (element, custom = {}) => {
    return Velocity(element, {
      opacity: 1
    }, {
      duration: custom.duration || duration,
      easing: custom.easing || easing
    });
  };
};

const fade = createFadeAnimation({ duration: 500 });

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

Композиция анимаций

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

const fadeIn = (el) =>
  Velocity(el, { opacity: 1 }, { duration: 200 });

const slideUp = (el) =>
  Velocity(el, { translateY: 0 }, { duration: 300 });

const fadeInSlideUp = async (el) => {
  await fadeIn(el);
  return slideUp(el);
};

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

Параллельное выполнение

Velocity.js поддерживает одновременные анимации через группы вызовов.

const animateCard = (card) => {
  Velocity(card.querySelector(".title"), { opacity: 1 }, { duration: 200 });

  Velocity(card.querySelector(".content"), {
    opacity: 1,
    translateY: 0
  }, {
    duration: 300
  });

  Velocity(card.querySelector(".actions"), {
    opacity: 1
  }, {
    duration: 150
  });
};

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

Хранилище анимационных пресетов

При большом количестве эффектов возникает необходимость централизованного реестра.

const animations = {
  fadeIn: (el) =>
    Velocity(el, { opacity: 1 }, { duration: 200 }),

  slideUp: (el) =>
    Velocity(el, { translateY: 0 }, { duration: 300 }),

  scaleIn: (el) =>
    Velocity(el, { scale: 1 }, { duration: 250 })
};

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

Динамическое применение пресетов

const runAnimation = (name, element) => {
  const animation = animations[name];
  if (!animation) return;

  return animation(element);
};

runAnimation("fadeIn", modal);

Появляется слой абстракции между логикой интерфейса и реализацией анимации.

Состояния и повторное использование

Переиспользуемые анимации часто связаны с состояниями интерфейса: открытие, закрытие, активное состояние, скрытие.

const modalAnimations = {
  open: (el) =>
    Velocity(el, {
      opacity: 1,
      scale: 1
    }, {
      duration: 300
    }),

  close: (el) =>
    Velocity(el, {
      opacity: 0,
      scale: 0.9
    }, {
      duration: 200
    })
};

Каждое состояние становится отдельным переиспользуемым блоком поведения.

Использование очередей для переиспользуемых сценариев

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

const openPanel = (el) => {
  Velocity(el, "fadeIn", { duration: 200 });
  Velocity(el, { height: "300px" }, { duration: 300 });
  Velocity(el, { opacity: 1 }, { duration: 150 });
};

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

Инкапсуляция DOM-логики

Часто переиспользуемая анимация включает подготовку DOM-состояния.

const prepareElement = (el, hiddenHeight) => {
  el.style.overflow = "hidden";
  el.style.height = hiddenHeight;
  el.style.opacity = 0;
  el.style.transform = "translateY(20px)";
};

const reveal = (el) => {
  prepareElement(el, "0px");

  return Velocity(el, {
    opacity: 1,
    translateY: 0,
    height: "auto"
  }, {
    duration: 350
  });
};

Подготовка состояния становится частью переиспользуемого блока.

Переиспользование через data-атрибуты

Анимации могут быть связаны с DOM-метаданными, что позволяет отделить логику от вызовов в коде.

document.querySelectorAll("[data-animate]").forEach((el) => {
  const type = el.dataset.animate;

  if (type === "fade-in") {
    Velocity(el, { opacity: 1 }, { duration: 200 });
  }

  if (type === "slide-up") {
    Velocity(el, { translateY: 0 }, { duration: 300 });
  }
});

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

Библиотека анимационных функций как слой архитектуры

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

export const uiAnimations = {
  fadeInUp: (el) =>
    Velocity(el, { opacity: 1, translateY: 0 }, { duration: 300 }),

  fadeOut: (el) =>
    Velocity(el, { opacity: 0 }, { duration: 200 }),

  popIn: (el) =>
    Velocity(el, { scale: 1 }, { duration: 250 })
};

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