Библиотека Vivus работает поверх SVG и управляет анимацией обводки
путей через манипуляцию атрибутами stroke-dasharray и
stroke-dashoffset. В Angular интеграция строится вокруг
жизненного цикла компонентов, поскольку инициализация Vivus требует уже
отрендеренного DOM-элемента с SVG.
Установка через npm:
npm install vivus
Библиотека не поставляет Angular-обёрток, поэтому используется прямой импорт:
import Vivus from 'vivus';
SVG-элементы должны быть доступны в DOM к моменту инициализации, иначе Vivus не сможет вычислить длины путей.
Основной способ работы — инициализация в AfterViewInit,
когда Angular гарантирует наличие DOM.
import { Component, AfterViewInit, ElementRef, ViewChild, OnDestroy } from '@angular/core';
import Vivus from 'vivus';
@Component({
selector: 'app-svg-animation',
template: `
<div #svgContainer>
<svg id="logo" viewBox="0 0 200 200">
<path d="M10 10 L190 10 L190 190 L10 190 Z" stroke="black" fill="none"/>
</svg>
</div>
`
})
export class SvgAnimationComponent implements AfterViewInit, OnDestroy {
@ViewChild('svgContainer', { static: false }) svgContainer!: ElementRef;
private vivusInstance?: Vivus;
ngAfterViewInit(): void {
this.vivusInstance = new Vivus(
'logo',
{
type: 'delayed',
duration: 120,
animTimingFunction: Vivus.EASE
}
);
}
ngOnDestroy(): void {
this.vivusInstance = undefined;
}
}
Ключевым моментом является передача идентификатора SVG-элемента или
DOM-ноды. Angular чаще использует id, поскольку это
упрощает доступ Vivus к элементу.
Работа через ElementRef даёт более строгий контроль над
DOM без привязки к глобальным идентификаторам.
@ViewChild('svgRef', { static: false }) svgRef!: ElementRef<SVGElement>;
ngAfterViewInit(): void {
this.vivusInstance = new Vivus(
this.svgRef.nativeElement,
{
type: 'sync',
duration: 100
}
);
}
HTML:
<svg #svgRef viewBox="0 0 300 300">
<path d="M50 150 Q150 50 250 150" stroke="black" fill="none"/>
</svg>
Такой подход снижает риск конфликтов идентификаторов при повторном использовании компонентов.
Vivus поддерживает несколько режимов, которые по-разному ведут себя в рамках Angular change detection.
Пути анимируются последовательно, с задержкой между сегментами. Подходит для логотипов и декоративных SVG.
new Vivus('logo', { type: 'delayed', duration: 150 });
Все пути анимируются одновременно. Используется для схем и графиков.
new Vivus('diagram', { type: 'sync', duration: 80 });
Каждый сегмент анимируется по очереди внутри одного пути, затем переходит к следующему.
new Vivus('illustration', { type: 'oneByOne', duration: 200 });
Angular часто пересоздаёт компоненты при изменении маршрутов или структурных директив. Vivus не всегда автоматически пересчитывает SVG, поэтому требуется ручной контроль.
restartAnimation(): void {
if (this.vivusInstance) {
this.vivusInstance.reset();
this.vivusInstance.play();
}
}
Методы:
reset() — возвращение к начальному состояниюplay() — повторный запускstop() — остановка текущей анимацииДля повторного использования логики создаётся директива, инкапсулирующая Vivus-инициализацию.
import { Directive, ElementRef, Input, AfterViewInit, OnDestroy } from '@angular/core';
import Vivus from 'vivus';
@Directive({
selector: '[vivusAnimate]'
})
export class VivusDirective implements AfterViewInit, OnDestroy {
@Input('vivusAnimate') options: Vivus.Options = {};
private instance?: Vivus;
constructor(private el: ElementRef) {}
ngAfterViewInit(): void {
this.instance = new Vivus(this.el.nativeElement, {
type: this.options.type || 'delayed',
duration: this.options.duration || 100
});
}
ngOnDestroy(): void {
this.instance = undefined;
}
}
Использование:
<svg vivusAnimate [vivusAnimate]="{ type: 'sync', duration: 90 }">
<path d="..." />
</svg>
Такой подход переносит всю логику работы с SVG-анимацией в декларативный слой Angular.
SVG может приходить из API или формироваться через
*ngFor. В таких случаях критично дождаться полного
рендеринга DOM.
<svg #dynamicSvg>
<ng-container *ngFor="let p of paths">
<path [attr.d]="p.d" stroke="black" fill="none"></path>
</ng-container>
</svg>
Инициализация выполняется после стабилизации view:
import { ChangeDetectorRef, AfterViewChecked } from '@angular/core';
private initialized = false;
ngAfterViewChecked(): void {
if (!this.initialized) {
this.initialized = true;
this.vivusInstance = new Vivus(this.svgRef.nativeElement, {
type: 'delayed',
duration: 120
});
}
}
Такой способ используется при сложной асинхронной отрисовке SVG-структуры.
При серверном рендеринге отсутствует DOM, поэтому прямое создание
Vivus приводит к ошибкам window is not defined.
Защита выполняется через проверку платформы:
import { isPlatformBrowser } from '@angular/common';
import { Inject, PLATFORM_ID } from '@angular/core';
constructor(@Inject(PLATFORM_ID) private platformId: object) {}
ngAfterViewInit(): void {
if (!isPlatformBrowser(this.platformId)) {
return;
}
this.vivusInstance = new Vivus('logo', {
type: 'sync',
duration: 100
});
}
Такое разделение позволяет сохранить SSR-совместимость без отключения клиентской анимации.
Vivus вычисляет длину каждого SVG-path, что может быть затратным при большом количестве элементов. В Angular это особенно заметно при частых перерендерах.
Практики оптимизации:
ChangeDetectionStrategy.OnPush<path> внутри одного
SVG@Component({
changeDetection: ChangeDetectionStrategy.OnPush
})
При переходах между маршрутами экземпляры Vivus должны корректно уничтожаться, иначе возможны утечки памяти.
ngOnDestroy(): void {
if (this.vivusInstance) {
this.vivusInstance.stop();
}
}
При возврате на страницу возможна повторная инициализация через
ngAfterViewInit.
Vivus может использоваться параллельно с
@angular/animations, однако важно разграничивать зоны
ответственности: Angular управляет контейнером, Vivus — внутренними
SVG-path.
trigger('fadeIn', [
transition(':enter', [
style({ opacity: 0 }),
animate('300ms ease-out', style({ opacity: 1 }))
])
]);
SVG-анимация запускается после завершения Angular-анимации контейнера, что обеспечивает визуальную согласованность.
В сложных интерфейсах может потребоваться независимая анимация нескольких SVG одновременно.
private instances: Vivus[] = [];
ngAfterViewInit(): void {
const svgs = document.querySelectorAll('svg[data-vivus]');
svgs.forEach(svg => {
this.instances.push(
new Vivus(svg, { type: 'delayed', duration: 100 })
);
});
}
Каждый экземпляр управляется отдельно, что позволяет синхронизировать или разносить анимации по времени.
При lazy-loading модулей Angular Vivus инициализируется только при активации маршрута. Это снижает нагрузку на старт приложения и предотвращает преждевременные вычисления SVG.
const routes: Routes = [
{
path: 'animation',
loadChildren: () =>
import('./animation/animation.module').then(m => m.AnimationModule)
}
];
Vivus-инициализация остаётся внутри компонента модуля и не влияет на основной бандл.