Интеграция с Vue.js

Связка Velocity.js и Vue.js строится вокруг управления состоянием и жизненного цикла компонентов. Vue отвечает за реактивность и рендеринг, Velocity — за высокопроизводительную анимацию DOM-элементов. Ключевая задача — корректно синхронизировать обновления виртуального DOM Vue с непосредственными манипуляциями DOM через Velocity.

Основные принципы:

  • Анимации запускаются после монтирования элемента (mounted)
  • При изменении реактивных данных используются watchers или computed-свойства
  • Перед удалением элемента необходимо управлять анимацией выхода (beforeUnmount)
  • Следует избегать конфликтов между Vue transition system и Velocity

Установка и базовая настройка

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>

Здесь важно, что начальные стили заданы до рендера, чтобы избежать “скачка” при появлении.


Реакция на изменения данных

Watcher-подход

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;
  }
}

Интеграция с директивами Vue

Создание кастомной директивы позволяет переиспользовать анимации:

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>

Преимущество — изоляция логики анимации и повторное использование.


Работа с списками и v-for

При анимации списков важно учитывать ключи (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);
      }
    });
  }
}

Интеграция с transition-компонентом Vue

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 не завершит переход.


Асинхронные анимации и Promise

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 без debounce
  • Использовать transform и opacity вместо layout-свойств
  • Группировать анимации через stagger
  • Минимизировать обращения к DOM через $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");

Это важно при:

  • переключении маршрутов
  • скрытии компонентов
  • повторных триггерах анимаций

Интеграция с Vue Router

При смене маршрутов анимации можно запускать в 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 });
});

Работа с Composition API

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");
}

Расширенные техники

Sequence-анимации

Velocity.RunSequence([
  { e: this.$refs.box, p: { opacity: 1 }, o: { duration: 300 } },
  { e: this.$refs.box, p: { translateX: 100 }, o: { duration: 300 } }
]);

Комбинирование с CSS

Velocity может работать вместе с CSS transition:

  • CSS — для простых эффектов
  • Velocity — для сложной логики и последовательностей

Масштабируемая архитектура

В крупных проектах анимации выносятся:

  • в отдельные модули
  • в composable-функции (Composition API)
  • в директивы

Пример 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();

Тестирование анимаций

При тестировании:

  • отключать анимации (duration: 0)
  • мокать Velocity
  • проверять вызовы, а не визуальный результат
Velocity.mock = true;

Управление состоянием через store

При использовании Vuex или Pinia:

watch(() => store.isOpen, (val) => {
  Velocity(el, val ? "slideDown" : "slideUp");
});

Анимация становится реакцией на глобальное состояние.


Итеративное улучшение UX

Velocity позволяет добавлять:

  • easing-функции
  • physics-based анимации
  • сложные цепочки

Пример:

Velocity(el, 
  { translateY: 0 }, 
  { duration: 500, easing: "easeOutBounce" }
);

Такие эффекты делают интерфейс более отзывчивым и “живым”, особенно при грамотной интеграции с реактивной моделью Vue.