Vue-директивы в контексте Motion One строятся как абстракция над
функцией animate() и системой управления анимациями через
motion-runtime. Директива становится связующим слоем между
жизненным циклом DOM-элемента во Vue и imperative API анимации.
Ключевая идея заключается в том, что директива:
elbinding.value,
binding.modifiers)В результате декларативная модель Vue превращается в управляемые анимационные состояния без ручного контроля таймингов.
Основной строительный блок интеграции — директива
v-motion, которая назначает анимационные свойства
элементу.
Типовая структура обработки:
mounted — инициализация начального состоянияupdated — реакция на изменение bindingunmounted — очистка анимацииЛогика работы:
import { animate } from "motion";
export const vMotion = {
mounted(el, binding) {
const config = binding.value;
if (config?.initial) {
Object.assign(el.style, config.initial);
}
el._motion = animate(
el,
config.animate,
config.options || {}
);
},
updated(el, binding) {
const config = binding.value;
el._motion?.cancel();
el._motion = animate(
el,
config.animate,
config.options || {}
);
},
unmounted(el) {
el._motion?.cancel();
}
};
v-motion="{
initial: { opacity: 0, transform: 'translateY(20px)' },
animate: { opacity: 1, transform: 'translateY(0px)' },
options: { duration: 0.6, easing: 'ease-out' }
}"
Директивный подход позволяет разделить анимацию на состояния, приближая его к state machine модели.
Типовые состояния:
Реализация через watcher внутри директивы:
updated(el, binding) {
const { state, variants } = binding.value;
const target = variants[state];
el._motion?.cancel();
el._motion = animate(el, target, {
duration: 0.4
});
}
Variants позволяют задавать набор предопределённых анимационных сценариев.
v-motion="{
state: 'active',
variants: {
inactive: { opacity: 0.3, scale: 0.98 },
active: { opacity: 1, scale: 1 }
}
}"
Особенности подхода:
Vue 3 Composition API влияет на архитектуру директив косвенно, через реактивные источники данных.
Типовой паттерн:
updatedwatch(() => state.value, (newState) => {
el._motion?.cancel();
el._motion = animate(el, variants[newState]);
});
Такой подход делает директиву продолжением реактивной системы, а не изолированным эффектом.
Motion One предоставляет богатую систему настройки переходов, которая
в директивах передаётся через options.
Основные параметры:
Пример:
v-motion="{
animate: { x: 100 },
options: {
duration: 1,
easing: 'ease-in-out',
delay: 0.2
}
}"
Параметры могут зависеть от состояния приложения:
options: computed(() => ({
duration: isFast.value ? 0.2 : 0.8
}))
В Vue часто требуется анимация появления и удаления элементов. Motion One позволяет реализовать это через директивные хуки.
v-motion="{
enter: { opacity: 1, y: 0 },
leave: { opacity: 0, y: -20 }
}"
Реализация:
mounted(el, binding) {
animate(el, binding.value.enter);
},
unmounted(el, binding) {
animate(el, binding.value.leave);
}
Особенность: анимация удаления требует задержки реального удаления
DOM, что обычно решается через nextTick и управление
v-if/v-show.
Motion One поддерживает scroll-driven animation, что в Vue удобно оборачивается в директиву.
v-motion-scroll="{
transform: {
inputRange: [0, 1],
outputRange: ['0px', '200px']
}
}"
Логика:
import { scroll } from "motion";
mounted(el, binding) {
el._scroll = scroll(progress => {
el.style.transform = `translateY(${progress * 200}px)`;
});
}
Создание собственных директив позволяет расширять систему под конкретные UI-паттерны.
export const vFade = {
mounted(el) {
animate(el, { opacity: [0, 1] }, { duration: 0.5 });
}
};
export const vSlideUp = {
mounted(el) {
animate(el, {
opacity: [0, 1],
transform: ["translateY(20px)", "translateY(0px)"]
});
}
};
Такие директивы формируют слой дизайн-системы, основанный на Motion One.
Директивы могут взаимодействовать с composables, формируя гибридную архитектуру.
Пример composable:
export function useMotionState() {
const state = ref("hidden");
const toggle = () => {
state.value = state.value === "hidden" ? "visible" : "hidden";
};
return { state, toggle };
}
Использование в директиве:
watch(state, (s) => {
animate(el, variants[s]);
});
При интенсивном использовании директив возникает необходимость оптимизации.
1. Отмена предыдущих анимаций
el._motion?.cancel();
2. Батчинг обновлений
Объединение изменений состояния в один frame:
requestAnimationFrame(() => {
animate(el, config);
});
3. Ограничение reflow
Минимизация изменения layout-свойств:
4. Переиспользование конфигураций
const presets = {
fastFade: { duration: 0.2 },
slowFade: { duration: 0.8 }
};
Директивы должны учитывать возможность отсутствия DOM или некорректных значений.
if (!el || !binding.value) return;
if (typeof binding.value !== "object") return;
Также важно защищать вызовы Motion One:
try {
animate(el, config);
} catch (e) {
console.warn("Motion error", e);
}