Работа с Angular

Использование Velocity.js в экосистеме Angular требует понимания принципов работы фреймворка: компонентной архитектуры, механизма обнаружения изменений (change detection) и управления жизненным циклом. Основная задача — корректно встроить анимации без нарушения реактивной модели Angular.

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


Установка и подключение

Velocity.js можно установить через npm:

npm install velocity-animate

Импорт в Angular-компоненте:

import Velocity from 'velocity-animate';

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

declare module 'velocity-animate';

Работа с DOM в Angular

Angular не рекомендует прямой доступ к DOM, но для анимаций это допустимо через ElementRef:

import { Component, ElementRef, AfterViewInit } from '@angular/core';

@Component({
  selector: 'app-box',
  template: `<div class="box"></div>`
})
export class BoxComponent implements AfterViewInit {

  constructor(private el: ElementRef) {}

  ngAfterViewInit() {
    const element = this.el.nativeElement.querySelector('.box');

    Velocity(element, { opacity: 1, translateY: 0 }, { duration: 500 });
  }
}

Ключевой момент — выполнение анимации в ngAfterViewInit, когда DOM уже доступен.


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

Для более безопасной работы с DOM применяется Renderer2. Однако Velocity.js требует прямой ссылки на элемент, поэтому Renderer используется только для получения элемента:

import { Renderer2, ElementRef } from '@angular/core';

constructor(private renderer: Renderer2, private el: ElementRef) {}

animate() {
  const element = this.el.nativeElement.querySelector('.box');
  Velocity(element, { scale: 1.2 });
}

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

Повторное использование логики удобно реализовать через директивы.

import { Directive, ElementRef, Input, OnInit } from '@angular/core';
import Velocity from 'velocity-animate';

@Directive({
  selector: '[appVelocity]'
})
export class VelocityDirective implements OnInit {

  @Input() animation: any;
  @Input() options: any;

  constructor(private el: ElementRef) {}

  ngOnInit() {
    Velocity(this.el.nativeElement, this.animation, this.options);
  }
}

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

<div
  appVelocity
  [animation]="{ opacity: 1, translateX: 100 }"
  [options]="{ duration: 300 }">
</div>

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


Синхронизация с жизненным циклом Angular

Анимации должны учитывать жизненный цикл компонентов:

  • ngOnInit — инициализация данных
  • ngAfterViewInit — доступ к DOM
  • ngOnDestroy — очистка

Пример остановки анимации при уничтожении компонента:

import { OnDestroy } from '@angular/core';

export class BoxComponent implements OnDestroy {

  element: any;

  ngAfterViewInit() {
    this.element = document.querySelector('.box');
    Velocity(this.element, { opacity: 1 });
  }

  ngOnDestroy() {
    Velocity(this.element, 'stop');
  }
}

Работа с событиями Angular

Velocity.js поддерживает callback-функции, которые можно использовать вместе с Angular-событиями:

Velocity(element, { opacity: 0 }, {
  duration: 500,
  complete: () => {
    this.isVisible = false;
  }
});

После завершения анимации изменяется состояние компонента, что инициирует обновление шаблона.


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

Angular использует Zone.js для отслеживания асинхронных операций. Velocity-анимации могут вызывать лишние циклы change detection.

Для оптимизации используется NgZone:

import { NgZone } from '@angular/core';

constructor(private zone: NgZone) {}

runAnimation(element: HTMLElement) {
  this.zone.runOutsideAngular(() => {
    Velocity(element, { translateX: 200 }, {
      complete: () => {
        this.zone.run(() => {
          this.animationDone = true;
        });
      }
    });
  });
}

Преимущества:

  • снижение нагрузки на change detection
  • повышение производительности

Комбинирование с Angular Animations

Angular имеет встроенную систему анимаций (@angular/animations). Velocity.js используется в случаях, когда:

  • требуется высокая производительность
  • нужны сложные последовательности
  • необходим контроль над easing-функциями

Пример комбинирования:

import { trigger, state, style, transition, animate } from '@angular/animations';

@Component({
  animations: [
    trigger('fade', [
      state('visible', style({ opacity: 1 })),
      transition('* => visible', [
        animate('300ms')
      ])
    ])
  ]
})

Velocity можно применять для более сложных сценариев, оставляя простые переходы Angular.


Последовательности анимаций

Velocity.js поддерживает цепочки анимаций:

Velocity(element, { opacity: 1 }, { duration: 300 })
  .then(() => Velocity(element, { translateX: 100 }))
  .then(() => Velocity(element, { scale: 1.5 }));

В Angular такие последовательности удобно вызывать в сервисах.


Создание сервиса для анимаций

Вынос логики в сервис повышает переиспользуемость:

import { Injectable } from '@angular/core';
import Velocity from 'velocity-animate';

@Injectable({
  providedIn: 'root'
})
export class AnimationService {

  fadeIn(element: HTMLElement) {
    return Velocity(element, { opacity: 1 }, { duration: 300 });
  }

  slide(element: HTMLElement) {
    return Velocity(element, { translateX: 200 });
  }
}

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

constructor(private animationService: AnimationService) {}

ngAfterViewInit() {
  this.animationService.fadeIn(this.el.nativeElement);
}

Работа с списками и *ngFor

При динамическом рендеринге элементов важно учитывать момент их появления:

@ViewChildren('item') items: QueryList<ElementRef>;

ngAfterViewInit() {
  this.items.forEach((item, index) => {
    Velocity(item.nativeElement, {
      opacity: 1,
      translateY: 0
    }, {
      delay: index * 100
    });
  });
}

Шаблон:

<div *ngFor="let item of list" #item class="list-item">
  {{ item }}
</div>

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

Velocity можно интегрировать с RxJS:

import { from } from 'rxjs';

from(Velocity(element, { opacity: 1 }))
  .subscribe(() => {
    console.log('Animation complete');
  });

Это позволяет включать анимации в реактивные цепочки.


Обработка ошибок

Velocity.js редко выбрасывает ошибки, но при работе с Angular важно учитывать:

  • элемент может отсутствовать
  • компонент может быть уничтожен

Проверка:

if (element) {
  Velocity(element, { opacity: 1 });
}

Кастомные easing-функции

Velocity поддерживает собственные easing:

Velocity(element, { translateX: 100 }, {
  easing: [0.17, 0.67, 0.83, 0.67]
});

Это позволяет добиться более плавных и сложных эффектов, чем стандартные Angular-анимации.


Работа с SVG в Angular

Velocity.js поддерживает анимации SVG:

Velocity(svgElement, { strokeDashoffset: 0 }, { duration: 1000 });

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


Управление состояниями анимаций

Можно хранить состояние анимации в компоненте:

isAnimating = false;

startAnimation(element: HTMLElement) {
  this.isAnimating = true;

  Velocity(element, { opacity: 1 }, {
    complete: () => {
      this.isAnimating = false;
    }
  });
}

Это полезно для блокировки UI или отображения загрузки.


Lazy Loading и анимации

При ленивой загрузке модулей важно убедиться, что Velocity импортирован в нужном модуле:

@NgModule({
  imports: [],
  declarations: []
})
export class FeatureModule {}

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


Анимации при маршрутизации

Angular Router позволяет отслеживать смену страниц:

import { Router, NavigationEnd } from '@angular/router';

constructor(private router: Router) {
  this.router.events.subscribe(event => {
    if (event instanceof NavigationEnd) {
      const element = document.querySelector('.page');
      Velocity(element, { opacity: 1 });
    }
  });
}

Дебаг и тестирование

Velocity-анимации можно отключать в тестах:

Velocity.mock = true;

Это ускоряет выполнение unit-тестов и предотвращает побочные эффекты.


Ограничения и особенности

  • прямой доступ к DOM нарушает абстракции Angular
  • требуется контроль жизненного цикла
  • возможны конфликты с Angular Animations
  • необходимо учитывать Zone.js

Практическая архитектура использования

Наиболее устойчивая схема:

  • директивы — для декларативных анимаций
  • сервисы — для бизнес-логики
  • NgZone — для оптимизации
  • lifecycle hooks — для синхронизации

Такой подход позволяет использовать Velocity.js в Angular-приложениях без потери производительности и управляемости кода.