Svelte actions

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

Motion One интегрируется с Svelte через actions за счёт своей минималистичной API и прямой работы с DOM. Основная идея заключается в том, что анимация не «встраивается» в компонентную логику, а навешивается на элемент как поведение, управляемое жизненным циклом action.


Svelte action — это функция следующего вида:

function action(node, params) {
  // инициализация

  return {
    update(newParams) {
      // реакция на изменение параметров
    },
    destroy() {
      // очистка
    }
  };
}

Node здесь — DOM-элемент, к которому применяется action. Params — конфигурация поведения.

Motion One использует императивную модель:

animate(element, { opacity: 1, transform: "translateY(0px)" })

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


Интеграция Motion One в Svelte action

Минимальная обёртка над animate:

import { animate } from "motion";

export function motion(node, params) {
  let animation;

  function runAnimation(config) {
    if (animation) animation.stop();

    animation = animate(node, config);
  }

  runAnimation(params);

  return {
    update(newParams) {
      runAnimation(newParams);
    },
    destroy() {
      if (animation) animation.stop();
    }
  };
}

Такая структура даёт базовую реактивность: при изменении параметров Svelte вызывает update, и анимация перезапускается.


Управление ключевыми кадрами через actions

Motion One поддерживает ключевые кадры в массивной форме, что удобно для декларативного описания движения:

animate(node, {
  transform: [
    "translateY(20px)",
    "translateY(0px)",
    "translateY(-10px)"
  ],
  opacity: [0, 1, 1]
}, {
  duration: 0.6,
  easing: "ease-out"
});

Svelte action может принимать такую структуру напрямую:

export function keyframeMotion(node, params) {
  let animation;

  const run = (p) => {
    if (animation) animation.stop();

    animation = animate(node, p.keyframes, p.options);
  };

  run(params);

  return {
    update(newParams) {
      run(newParams);
    },
    destroy() {
      animation?.stop();
    }
  };
}

Использование такого подхода позволяет полностью отделить описание движения от логики компонента.


Реактивные параметры и пересборка анимации

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

Типичный кейс — изменение длительности или easing:

export function motion(node, params) {
  let animation;

  const start = (p) => {
    animation?.stop();

    animation = animate(node, p.to, {
      duration: p.duration,
      easing: p.easing
    });
  };

  start(params);

  return {
    update(newParams) {
      start(newParams);
    },
    destroy() {
      animation?.stop();
    }
  };
}

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


Интеграция с событиями жизненного цикла DOM

Motion One поддерживает события onComplete, onUpdate, которые можно встроить в action для синхронизации состояния приложения:

export function motion(node, params) {
  let animation;

  const start = (p) => {
    animation?.stop();

    animation = animate(node, p.to, {
      duration: p.duration,
      easing: p.easing,
      onComplete: () => {
        p.onComplete?.();
      },
      onUpdate: (latest) => {
        p.onUpdate?.(latest);
      }
    });
  };

  start(params);

  return {
    update(newParams) {
      start(newParams);
    },
    destroy() {
      animation?.stop();
    }
  };
}

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


Параметризация через Svelte stores

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

Пример использования writable store:

import { writable } from "svelte/store";

export const motionState = writable({
  x: 0,
  opacity: 0
});

Action может подписываться на store:

import { animate } from "motion";
import { get } from "svelte/store";

export function motionStore(node, store) {
  let animation;

  const run = (state) => {
    animation?.stop();

    animation = animate(node, state, {
      duration: 0.4
    });
  };

  run(get(store));

  const unsubscribe = store.subscribe((value) => {
    run(value);
  });

  return {
    destroy() {
      unsubscribe();
      animation?.stop();
    }
  };
}

Такой подход превращает анимацию в реакцию на поток данных.


Scroll-driven анимации через actions

Motion One предоставляет инструменты scroll tracking, которые можно обернуть в Svelte action. Основная идея — привязать анимацию к прокрутке без ручного управления event listeners.

import { scroll } from "motion";

export function scrollMotion(node, params) {
  const controls = scroll((progress) => {
    node.style.transform = `translateY(${progress * 100}px)`;
    node.style.opacity = `${progress}`;
  }, {
    target: node,
    offset: ["start end", "end start"]
  });

  return {
    destroy() {
      controls?.cancel();
    }
  };
}

В этом сценарии action выступает как инкапсуляция подписки на scroll timeline.


View-based анимации (inView)

Анимации появления элементов часто реализуются через Intersection Observer. Motion One предоставляет абстракцию inView, которую удобно встроить в Svelte action:

import { inView, animate } from "motion";

export function inViewMotion(node, params) {
  const controls = inView(node, () => {
    const animation = animate(node, params.to, {
      duration: params.duration ?? 0.5,
      easing: params.easing ?? "ease-out"
    });

    return () => animation.stop();
  });

  return {
    destroy() {
      controls?.();
    }
  };
}

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


Композиция нескольких анимационных слоёв

Svelte actions могут быть скомбинированы на одном элементе через последовательное применение функций. Motion One хорошо ложится на этот паттерн, если разделять ответственность:

  • один action управляет входной анимацией
  • второй — hover-состоянием
  • третий — scroll эффектом

Пример разделения:

export function hoverMotion(node) {
  const enter = () => animate(node, { scale: 1.05 }, { duration: 0.2 });
  const leave = () => animate(node, { scale: 1 }, { duration: 0.2 });

  node.addEventListener("mouseenter", enter);
  node.addEventListener("mouseleave", leave);

  return {
    destroy() {
      node.removeEventListener("mouseenter", enter);
      node.removeEventListener("mouseleave", leave);
    }
  };
}

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


Управление конфликтами анимаций

При использовании нескольких actions или частых обновлениях параметров возникает проблема конкурирующих анимаций. Motion One решает это через явное управление экземпляром animation.

Типовая стратегия:

  • всегда хранить ссылку на текущую анимацию
  • останавливать предыдущую перед запуском новой
  • избегать параллельных animate на одном свойстве
let animation;

function safeAnimate(node, to, options) {
  animation?.stop();
  animation = animate(node, to, options);
}

Это становится базовым строительным блоком для любых Svelte actions с Motion One.


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

Svelte actions работают напрямую с DOM, минуя виртуальный слой. Motion One использует requestAnimationFrame и Web Animations API (или fallback), поэтому важно избегать лишних пересозданий анимаций.

Практически критично:

  • не пересоздавать animation при каждом мелком изменении параметров
  • разделять стабильные и изменяемые параметры
  • избегать глубоких объектов в params без необходимости

Оптимизированный паттерн:

export function motion(node, params) {
  let animation;
  let lastTo;

  const start = (to) => {
    if (JSON.stringify(to) === JSON.stringify(lastTo)) return;

    lastTo = to;

    animation?.stop();
    animation = animate(node, to, { duration: 0.4 });
  };

  start(params.to);

  return {
    update(newParams) {
      start(newParams.to);
    },
    destroy() {
      animation?.stop();
    }
  };
}

Использование timeline-подхода внутри actions

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

import { timeline } from "motion";

export function timelineMotion(node, params) {
  const sequence = timeline([
    [node, { opacity: 1 }, { duration: 0.2 }],
    [node, { transform: "translateY(0px)" }, { duration: 0.4 }]
  ]);

  return {
    destroy() {
      sequence?.stop();
    }
  };
}

В Svelte action этот подход полезен для одноразовых сложных входных анимаций.


Абстракция уровня дизайн-системы

Motion One в связке с Svelte actions часто используется для создания повторно используемых анимационных примитивов дизайн-системы. В этом случае action становится не просто обёрткой, а контрактом поведения.

Пример параметризуемого action:

export function presetMotion(node, { preset }) {
  const presets = {
    fade: { opacity: [0, 1] },
    slideUp: { transform: ["translateY(20px)", "translateY(0px)"] }
  };

  const animation = animate(node, presets[preset], {
    duration: 0.5
  });

  return {
    destroy() {
      animation.stop();
    }
  };
}

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


Согласование состояния UI и анимации

При работе с реактивными UI часто возникает рассинхронизация между состоянием компонента и текущей фазой анимации. Svelte actions с Motion One позволяют решать эту проблему через явные callbacks и контролируемый lifecycle.

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

В такой модели Motion One становится исполнительным слоем, а Svelte action — адаптером между декларативной UI-логикой и императивной анимацией DOM.