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

Библиотека Vivus позволяет анимировать SVG так, будто он «рисуется» в реальном времени, последовательно обводя контуры путей. В связке с Svelte она приобретает особенно удобную модель интеграции за счёт реактивности и жизненного цикла компонентов.

Библиотека устанавливается стандартным способом через npm:

npm install vivus

После установки экземпляр Vivus создаётся на основе DOM-элемента SVG, в котором должны быть пути (<path>), линии или контуры, поддерживающие stroke-анимацию.

В Svelte модуль импортируется напрямую:

import Vivus from 'vivus';

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

Подготовка SVG для анимации

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
  • отсутствие сложных фильтров, мешающих обводке
  • оптимизированные пути (часто через SVG-редакторы)

Инициализация в Svelte через onMount

Основной механизм интеграции — функция 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

Vivus поддерживает несколько режимов отрисовки:

Delayed

Каждый путь анимируется с задержкой относительно предыдущего. Создаёт эффект последовательного рисования.

type: 'delayed'

Sync

Все пути анимируются одновременно. Используется для логотипов с равномерной прорисовкой.

type: 'sync'

OneByOne

Каждый сегмент пути отрисовывается по очереди, создавая более детализированный эффект «рукописного» рисования.

type: 'oneByOne'

Управление SVG через Svelte-реактивность

В 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.

Использование Svelte actions для инкапсуляции

Более чистый подход — вынос логики в 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 и улучшает переиспользуемость.

Работа в SvelteKit и SSR

В средах с серверным рендерингом 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-анимаций

При работе с несколькими 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 для Vivus в Svelte-приложениях

Качество анимации напрямую зависит от структуры SVG:

  • минимизация количества узлов path
  • использование SVG-оптимизаторов (SVGO)
  • объединение сегментов контуров
  • избегание вложенных групп с трансформациями

В сложных интерфейсах Svelte это критично, поскольку лишние узлы увеличивают стоимость анимации и замедляют перерисовку.

Интеграция с переходами 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.

Повторное использование компонентов с Vivus

В 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, где компоненты часто монтируются и размонтируются без перезагрузки страницы.