Абстракция повторяющихся паттернов

При масштабировании интерфейсов на основе анимаций быстро возникает повторяемость: одинаковые переходы появления, схожие движения карточек, однотипные эффекты наведения, идентичные последовательности для списков. Прямое копирование конфигураций приводит к дублированию параметров duration, easing, delay, а также к расхождению поведения при последующих изменениях.

Motion One предоставляет набор примитивов, которые позволяют выстраивать уровень абстракции поверх низкоуровневых вызовов animate(), timeline() и inView(), формируя повторно используемые паттерны поведения вместо разрозненных деклараций.


Базовый уровень: конфигурации как атомарные паттерны

В основе большинства анимаций лежит объект параметров. Даже простая анимация появления уже содержит набор повторяемых характеристик:

  • длительность
  • кривую плавности
  • задержку
  • степень заполнения промежуточных кадров

Типичный фрагмент без абстракции:

import { animate } from "motion"

animate(
  ".card",
  { opacity: [0, 1], transform: ["translateY(10px)", "translateY(0px)"] },
  { duration: 0.4, easing: "ease-out" }
)

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


Профили анимации как средство унификации поведения

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

const fadeUp = {
  keyframes: {
    opacity: [0, 1],
    transform: ["translateY(12px)", "translateY(0px)"]
  },
  options: {
    duration: 0.45,
    easing: "ease-out"
  }
}

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

import { animate } from "motion"

animate(".card", fadeUp.keyframes, fadeUp.options)

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


Фабрики анимаций для параметризации поведения

Профили недостаточны, когда требуется вариативность: разные задержки, масштабы, направления движения. В этом случае вводится уровень фабрик.

Фабрика анимации возвращает конфигурацию на основе входных параметров:

const createFadeUp = (distance = 12, duration = 0.45) => ({
  keyframes: {
    opacity: [0, 1],
    transform: [`translateY(${distance}px)`, "translateY(0px)"]
  },
  options: {
    duration,
    easing: "ease-out"
  }
})

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

animate(".card", createFadeUp(20).keyframes, createFadeUp(20).options)

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


Композиция анимационных блоков

Сложные интерфейсы редко состоят из одного движения. Чаще используется комбинация эффектов: появление, смещение, масштабирование, синхронные задержки.

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

const fade = {
  opacity: [0, 1]
}

const slideUp = (y = 10) => ({
  transform: [`translateY(${y}px)`, "translateY(0px)"]
})

const composed = (y) => ({
  keyframes: {
    ...fade,
    ...slideUp(y)
  },
  options: {
    duration: 0.5,
    easing: "ease-out"
  }
})

Здесь происходит не просто объединение эффектов, а формирование композиционного паттерна, где базовые движения становятся строительными блоками.


Наследование параметров через базовые пресеты

При росте числа анимаций возникает необходимость в каскадной системе настроек. Базовый пресет задаёт общие параметры, поверх которых строятся специализированные варианты.

const base = {
  duration: 0.4,
  easing: "ease-out"
}

const fadeUp = (distance = 10) => ({
  keyframes: {
    opacity: [0, 1],
    transform: [`translateY(${distance}px)`, "translateY(0px)"]
  },
  options: base
})

В этом подходе базовый объект становится аналогом дизайн-системы для движения, где любые изменения касаются всех производных анимаций одновременно.


Абстракция задержек и последовательностей

Одним из наиболее повторяемых паттернов являются stagger-анимации списков. Вместо ручного указания delay для каждого элемента вводится вычисляемая модель.

const stagger = (index, step = 0.05) => index * step

Применение:

import { animate } from "motion"

document.querySelectorAll(".item").forEach((el, i) => {
  animate(
    el,
    { opacity: [0, 1], transform: ["translateY(8px)", "translateY(0px)"] },
    { delay: stagger(i) }
  )
})

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


Абстракция временных шкал через фабрики timeline

Motion One предоставляет timeline() как инструмент координации сложных сцен. Повторяемость проявляется в типовых сценариях: появление блока, затем заголовка, затем контента.

import { timeline } from "motion"

const createRevealTimeline = (container) => {
  const el = container.querySelectorAll(".part")

  return timeline([
    [el[0], { opacity: [0, 1] }, { duration: 0.3 }],
    [el[1], { opacity: [0, 1], transform: ["translateY(10px)", "translateY(0px)"] }, { at: "-0.1", duration: 0.4 }],
    [el[2], { opacity: [0, 1] }, { at: "-0.2", duration: 0.5 }]
  ])
}

Фабрика timeline превращает последовательность действий в повторяемую структуру, которую можно применять к разным контейнерам без переписывания логики.


Абстракция через функции поведения элементов

Вместо описания анимации как объекта всё чаще используется модель «поведение элемента», где анимация является функцией состояния.

const applyHoverMotion = (el) => {
  el.addEventListener("mouseenter", () => {
    animate(el, { scale: 1.05 }, { duration: 0.2 })
  })

  el.addEventListener("mouseleave", () => {
    animate(el, { scale: 1 }, { duration: 0.2 })
  })
}

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


Унификация ключевых кадров через словари состояний

При множестве UI-состояний (open, close, active, idle) возникает необходимость унифицировать переходы.

const states = {
  hidden: { opacity: 0, transform: "translateY(8px)" },
  visible: { opacity: 1, transform: "translateY(0px)" },
  exit: { opacity: 0, transform: "translateY(-8px)" }
}

Применение:

animate(".panel", states.hidden, { duration: 0 })
animate(".panel", states.visible, { duration: 0.4, easing: "ease-out" })

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


Абстракция через CSS-переменные как мост между слоями

Motion One тесно интегрируется с CSS, что позволяет переносить повторяющиеся параметры в переменные.

:root {
  --motion-duration: 0.4s;
  --motion-easing: ease-out;
}
animate(
  ".box",
  { opacity: [0, 1] },
  {
    duration: "var(--motion-duration)",
    easing: "var(--motion-easing)"
  }
)

Таким образом, анимационные паттерны становятся частью общей системы стилей, а не изолированным JavaScript-слоем.


Абстракция через генераторы списочных анимаций

Списки элементов часто требуют одинакового поведения. Генерация избавляет от повторяющегося обхода DOM.

const animateList = (selector, animationFactory) => {
  document.querySelectorAll(selector).forEach((el, i) => {
    const config = animationFactory(i, el)

    animate(el, config.keyframes, config.options)
  })
}

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

animateList(".card", (i) => ({
  keyframes: { opacity: [0, 1], transform: ["translateY(10px)", "translateY(0px)"] },
  options: { delay: i * 0.05, duration: 0.4 }
}))

Функция становится универсальным адаптером между DOM-структурой и анимационной логикой.


Слой доменных анимаций вместо технических конфигураций

На уровне архитектуры выделяется переход от «как анимировать» к «что происходит». Вместо описания параметров создаются доменные функции:

const enterCard = (el) => animate(el, { opacity: [0, 1], y: [10, 0] }, { duration: 0.4 })
const exitCard = (el) => animate(el, { opacity: [1, 0], y: [0, -10] }, { duration: 0.3 })

Дальнейшая работа с интерфейсом оперирует уже смысловыми действиями, а не техническими характеристиками.


Уровень повторного использования как градиент архитектуры

Абстракция повторяющихся паттернов формируется не одним механизмом, а совокупностью слоёв:

  • базовые профили параметров
  • фабрики конфигураций
  • композиция keyframes
  • генераторы списков
  • словари состояний
  • доменные функции поведения

Каждый слой уменьшает количество повторяющегося кода и увеличивает уровень выражения логики движения, смещая её от технических деталей к структурным правилам поведения интерфейса