Библиотека Vivus позволяет анимировать SVG так, будто он «рисуется» в реальном времени, последовательно обводя контуры путей. В связке с Svelte она приобретает особенно удобную модель интеграции за счёт реактивности и жизненного цикла компонентов.
Библиотека устанавливается стандартным способом через npm:
npm install vivus
После установки экземпляр Vivus создаётся на основе DOM-элемента SVG,
в котором должны быть пути (<path>), линии или
контуры, поддерживающие stroke-анимацию.
В Svelte модуль импортируется напрямую:
import Vivus from 'vivus';
SVG должен быть доступен в DOM к моменту инициализации, поэтому ключевая точка интеграции — жизненный цикл компонента.
Vivus работает только с SVG, содержащими обводимые элементы. Наиболее
важный момент — наличие stroke вместо заливки:
<svg id="logo" viewBox="0 0 200 200">
<path d="M10 10 H 190 V 190 H 10 Z" />
</svg>
Для корректного эффекта обычно требуется:
fill="none" или минимизация заливкиstrokeОсновной механизм интеграции — функция onMount,
гарантирующая доступ к DOM.
<script>
import { onMount } from 'svelte';
import Vivus from 'vivus';
let svgElement;
onMount(() => {
const animation = new Vivus(svgElement, {
duration: 120,
type: 'delayed',
animTimingFunction: Vivus.EASE
});
return () => {
animation.stop();
};
});
</script>
<svg bind:this={svgElement} viewBox="0 0 200 200">
<path d="M10 10 H 190 V 190 H 10 Z" />
</svg>
Ключевой момент — использование bind:this, позволяющего
получить ссылку на DOM-узел SVG без querySelector.
Vivus поддерживает несколько режимов отрисовки:
Каждый путь анимируется с задержкой относительно предыдущего. Создаёт эффект последовательного рисования.
type: 'delayed'
Все пути анимируются одновременно. Используется для логотипов с равномерной прорисовкой.
type: 'sync'
Каждый сегмент пути отрисовывается по очереди, создавая более детализированный эффект «рукописного» рисования.
type: 'oneByOne'
В Svelte удобно связывать параметры анимации с реактивными переменными.
<script>
import { onMount } from 'svelte';
import Vivus from 'vivus';
let svgElement;
let duration = 80;
let animation;
</script>
<sel ect bind:value={duration}>
<option value="40">Быстро</option>
<option value="80">Средне</option>
<option value="150">Медленно</option>
</select>
<svg bind:this={svgElement} viewBox="0 0 200 200">
<path d="M20 20 H 180 V 180 H 20 Z" />
</svg>
<script>
onMount(() => {
animation = new Vivus(svgElement, {
duration,
type: 'sync'
});
});
</script>
Важно учитывать, что изменение duration после
инициализации не влияет на уже созданный экземпляр. Для динамической
смены параметров требуется пересоздание.
В Svelte реактивные блоки позволяют контролировать пересоздание Vivus:
<script>
import { onMount } fr om 'svelte';
import Vivus from 'vivus';
let svgElement;
let animation;
let key = 0;
function restart() {
key += 1;
}
onMount(() => {
animation = new Vivus(svgElement, {
duration: 100,
type: 'delayed'
});
});
</script>
<button on:click={restart}>Перезапустить</button>
{#key key}
<svg bind:this={svgElement} viewBox="0 0 200 200">
<path d="M10 10 H 190 V 190 H 10 Z" />
</svg>
{/key}
Использование {#key} заставляет Svelte полностью
пересоздать SVG, что автоматически приводит к новой инициализации
Vivus.
Более чистый подход — вынос логики в action:
import Vivus from 'vivus';
export function vivus(node, options) {
const animation = new Vivus(node, options);
return {
destroy() {
animation.stop();
}
};
}
Применение в компоненте:
<script>
import { vivus } from './vivus-action.js';
</script>
<svg use:vivus={{ duration: 120, type: 'oneByOne' }} viewBox="0 0 200 200">
<path d="M20 20 H 180 V 180 H 20 Z" />
</svg>
Такой подход избавляет от необходимости вручную использовать
onMount и улучшает переиспользуемость.
В средах с серверным рендерингом Vivus должен инициализироваться
только в браузере, так как библиотека зависит от window и
DOM.
<script>
import { browser } from '$app/environment';
import { onMount } from 'svelte';
import Vivus from 'vivus';
let svgElement;
onMount(() => {
if (!browser) return;
new Vivus(svgElement, {
duration: 100,
type: 'sync'
});
});
</script>
Ключевой момент — предотвращение выполнения кода на сервере.
Vivus можно связывать с состоянием интерфейса, например, запускать анимацию при изменении состояния:
<script>
import { onMount } from 'svelte';
import Vivus from 'vivus';
let svgElement;
let active = false;
let animation;
$: if (active && animation) {
animation.play(1);
}
</script>
<button on:click={() => active = !active}>
Переключить
</button>
<svg bind:this={svgElement} viewBox="0 0 200 200">
<path d="M10 10 H 190 V 190 H 10 Z" />
</svg>
<script>
onMount(() => {
animation = new Vivus(svgElement, {
duration: 80,
type: 'delayed'
});
});
</script>
Vivus предоставляет методы управления анимацией:
play(speed)stop()reset()finish()Это позволяет интегрировать SVG-анимации в сложные UI-сценарии.
При работе с несколькими SVG важно учитывать независимость экземпляров Vivus:
<script>
import { onMount } from 'svelte';
import Vivus from 'vivus';
let icons = [];
</script>
{#each [1,2,3] as item}
<svg bind:this={el => icons[item] = el} viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" />
</svg>
{/each}
<script>
onMount(() => {
icons.forEach(el => {
new Vivus(el, {
duration: 60,
type: 'sync'
});
});
});
</script>
Каждый SVG получает отдельный экземпляр, что позволяет независимо управлять анимациями.
Качество анимации напрямую зависит от структуры SVG:
В сложных интерфейсах Svelte это критично, поскольку лишние узлы увеличивают стоимость анимации и замедляют перерисовку.
Vivus можно синхронизировать с переходами появления компонентов:
<script>
import { fade } from 'svelte/transition';
import Vivus from 'vivus';
import { onMount } from 'svelte';
let svgElement;
onMount(() => {
new Vivus(svgElement, {
duration: 100,
type: 'oneByOne'
});
});
</script>
<div transition:fade>
<svg bind:this={svgElement} viewBox="0 0 200 200">
<path d="M10 10 H 190 V 190 H 10 Z" />
</svg>
</div>
Таким образом анимация SVG запускается синхронно с появлением элемента в DOM.
В Svelte компоненты с Vivus удобно инкапсулировать:
<script>
import Vivus from 'vivus';
import { onMount } from 'svelte';
export let duration = 100;
let svgElement;
onMount(() => {
new Vivus(svgElement, { duration, type: 'sync' });
});
</script>
<svg bind:this={svgElement} viewBox="0 0 200 200">
<slot />
</svg>
Такой компонент позволяет передавать SVG-контент через слот, сохраняя единую логику анимации.
При интенсивном использовании SVG-анимаций важно избегать утечек памяти. Vivus не всегда автоматически освобождает ресурсы, поэтому при уничтожении компонента следует останавливать анимацию или обнулять ссылки.
return {
destroy() {
animation.stop();
animation = null;
}
};
Это особенно важно в SPA на Svelte, где компоненты часто монтируются и размонтируются без перезагрузки страницы.