Связка Velocity.js и Vue.js строится вокруг управления состоянием и жизненного цикла компонентов. Vue отвечает за реактивность и рендеринг, Velocity — за высокопроизводительную анимацию DOM-элементов. Ключевая задача — корректно синхронизировать обновления виртуального DOM Vue с непосредственными манипуляциями DOM через Velocity.
Основные принципы:
mounted)beforeUnmount)npm install velocity-animate
Подключение в компоненте:
import Velocity from 'velocity-animate';
export default {
mounted() {
Velocity(this.$refs.box, { opacity: 1, translateY: 0 }, { duration: 500 });
}
}
<div ref="box" style="opacity:0; transform: translateY(20px);"></div>
Здесь важно, что начальные стили заданы до рендера, чтобы избежать “скачка” при появлении.
export default {
data() {
return {
isVisible: false
};
},
watch: {
isVisible(newValue) {
if (newValue) {
Velocity(this.$refs.box, "fadeIn", { duration: 300 });
} else {
Velocity(this.$refs.box, "fadeOut", { duration: 300 });
}
}
}
}
methods: {
toggle() {
this.isVisible = !this.isVisible;
}
}
Создание кастомной директивы позволяет переиспользовать анимации:
app.directive('velocity-fade', {
mounted(el) {
Velocity(el, { opacity: 1 }, { duration: 500 });
},
beforeUnmount(el) {
Velocity(el, { opacity: 0 }, { duration: 300 });
}
});
Использование:
<div v-velocity-fade></div>
Преимущество — изоляция логики анимации и повторное использование.
При анимации списков важно учитывать ключи (key) и
порядок элементов.
<div v-for="item in items" :key="item.id" ref="items">
{{ item.text }}
</div>
Анимация добавления:
this.$nextTick(() => {
const elements = this.$refs.items;
Velocity(elements, "transition.slideUpIn", { stagger: 100 });
});
Анимация удаления требует предварительной обработки:
methods: {
removeItem(index) {
const el = this.$refs.items[index];
Velocity(el, "fadeOut", {
duration: 300,
complete: () => {
this.items.splice(index, 1);
}
});
}
}
Vue предоставляет <transition>, но стандартные
CSS-анимации можно заменить на Velocity:
<transition
@enter="enter"
@leave="leave"
>
<div v-if="visible"></div>
</transition>
methods: {
enter(el, done) {
Velocity(el, { opacity: 1, translateY: 0 }, { duration: 400, complete: done });
},
leave(el, done) {
Velocity(el, { opacity: 0, translateY: -20 }, { duration: 300, complete: done });
}
}
Ключевой момент — вызов done, иначе Vue не завершит
переход.
Velocity поддерживает Promise-интерфейс:
async animateSequence() {
await Velocity(this.$refs.box, { translateX: 100 }, { duration: 300 });
await Velocity(this.$refs.box, { translateY: 100 }, { duration: 300 });
await Velocity(this.$refs.box, { translateX: 0, translateY: 0 }, { duration: 300 });
}
Это упрощает сложные цепочки анимаций.
watch без
debouncetransform и opacity вместо
layout-свойствstagger$refsПример оптимизированной анимации:
Velocity(this.$refs.items,
{ opacity: 1, translateY: 0 },
{ duration: 400, stagger: 50 }
);
Velocity позволяет останавливать и контролировать анимации:
Velocity(this.$refs.box, "stop");
Пауза и возобновление:
Velocity(this.$refs.box, "pause");
Velocity(this.$refs.box, "resume");
Это важно при:
При смене маршрутов анимации можно запускать в navigation guards:
router.beforeEach((to, from, next) => {
const appEl = document.getElementById('app');
Velocity(appEl, { opacity: 0 }, {
duration: 200,
complete: next
});
});
После перехода:
router.afterEach(() => {
const appEl = document.getElementById('app');
Velocity(appEl, { opacity: 1 }, { duration: 200 });
});
import { onMounted, ref } from 'vue';
import Velocity from 'velocity-animate';
export default {
setup() {
const box = ref(null);
onMounted(() => {
Velocity(box.value, { scale: 1 }, { duration: 300 });
});
return { box };
}
}
<div ref="box" style="transform: scale(0)"></div>
Конфликт с реактивностью
Прямая манипуляция DOM может нарушить ожидаемое поведение Vue.
Решение — использовать $refs и не менять DOM-структуру
вручную.
Повторные анимации
Если компонент перерендеривается, анимация может запускаться повторно. Контроль через флаги:
data() {
return {
animated: false
};
}
Отсутствие cleanup
Перед уничтожением компонента:
beforeUnmount() {
Velocity(this.$refs.box, "stop");
}
Velocity.RunSequence([
{ e: this.$refs.box, p: { opacity: 1 }, o: { duration: 300 } },
{ e: this.$refs.box, p: { translateX: 100 }, o: { duration: 300 } }
]);
Velocity может работать вместе с CSS transition:
В крупных проектах анимации выносятся:
Пример composable:
import Velocity from 'velocity-animate';
export function useFade() {
const fadeIn = (el) => {
return Velocity(el, { opacity: 1 }, { duration: 300 });
};
const fadeOut = (el) => {
return Velocity(el, { opacity: 0 }, { duration: 300 });
};
return { fadeIn, fadeOut };
}
Использование:
const { fadeIn, fadeOut } = useFade();
При тестировании:
Velocity.mock = true;
При использовании Vuex или Pinia:
watch(() => store.isOpen, (val) => {
Velocity(el, val ? "slideDown" : "slideUp");
});
Анимация становится реакцией на глобальное состояние.
Velocity позволяет добавлять:
Пример:
Velocity(el,
{ translateY: 0 },
{ duration: 500, easing: "easeOutBounce" }
);
Такие эффекты делают интерфейс более отзывчивым и “живым”, особенно при грамотной интеграции с реактивной моделью Vue.