Режим script

В Vivus Vivus режим script отличается тем, что полностью передаёт управление анимацией внешнему коду. В отличие от автоматических стратегий отрисовки (delayed, sync, oneByOne), здесь библиотека не выполняет интерполяцию сама, а лишь подготавливает SVG-элементы и предоставляет инструменты для ручного контроля прогресса.

Основная идея режима заключается в том, что разработчик самостоятельно определяет момент, скорость и логику изменения параметров отрисовки, обычно через stroke-dasharray и stroke-dashoffset. Vivus в этом сценарии выступает как слой инициализации и утилита для работы с длинами путей.


При использовании type: 'script' библиотека:

  • не запускает автоматическую анимацию;
  • вычисляет длины всех SVG-путей;
  • устанавливает начальные значения stroke-dasharray;
  • оставляет управление stroke-dashoffset внешнему коду;
  • предоставляет доступ к DOM-структуре SVG через экземпляр Vivus.

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


Инициализация SVG в script-режиме

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

const animation = new Vivus('my-svg', {
  type: 'script',
  duration: 0
});

Ключевой момент — duration в данном режиме не влияет на поведение анимации и может использоваться как вспомогательное значение для собственной логики тайминга.

После инициализации все пути SVG уже подготовлены, но визуально остаются в начальном состоянии (обычно полностью скрыты через stroke-dashoffset).


Структура управления отрисовкой

Каждый SVG-путь внутри Vivus имеет вычисленную длину:

const length = path.getTotalLength();

Библиотека применяет:

stroke-dasharray: length;
stroke-dashoffset: length;

Дальнейшее изменение stroke-dashoffset от length до 0 и формирует эффект прорисовки линии.

В режиме script этот процесс не автоматизирован, поэтому управление сводится к прямому изменению свойства:

path.style.strokeDashoffset = value;

Получение доступа к элементам SVG

Экземпляр Vivus содержит ссылки на элементы, сгруппированные по структуре SVG:

animation.el // корневой SVG элемент
animation.map // массив путей

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

Пример обхода:

animation.map.forEach(item => {
  item.el.style.strokeDashoffset = 100;
});

Построение ручной анимации

Основной механизм управления заключается в создании собственного цикла обновления.

Базовая схема:

let progress = 0;

function animate() {
  progress += 0.01;

  animation.map.forEach(item => {
    const length = item.length;
    item.el.style.strokeDashoffset = length * (1 - progress);
  });

  if (progress < 1) {
    requestAnimationFrame(animate);
  }
}

animate();

Здесь progress выступает нормализованным коэффициентом от 0 до 1, который определяет степень прорисовки.


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

Оптимальная реализация всегда строится вокруг requestAnimationFrame, поскольку SVG-изменения должны синхронизироваться с циклом рендеринга браузера.

function step(timestamp) {
  const duration = 2000;
  const start = performance.now();

  function frame(now) {
    const elapsed = now - start;
    const t = Math.min(elapsed / duration, 1);

    animation.map.forEach(item => {
      item.el.style.strokeDashoffset = item.length * (1 - t);
    });

    if (t < 1) {
      requestAnimationFrame(frame);
    }
  }

  requestAnimationFrame(frame);
}

step();

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


Работа с несколькими путями

SVG часто содержит сложную структуру из множества контуров. В script-режиме можно управлять каждым элементом отдельно, создавая эффекты последовательного или синхронного появления.

Последовательная отрисовка:

let index = 0;

function drawNext() {
  if (index >= animation.map.length) return;

  const item = animation.map[index];
  let progress = 0;

  function step() {
    progress += 0.02;

    item.el.style.strokeDashoffset =
      item.length * (1 - Math.min(progress, 1));

    if (progress < 1) {
      requestAnimationFrame(step);
    } else {
      index++;
      drawNext();
    }
  }

  step();
}

drawNext();

Интеграция с пользовательской логикой

Script-режим часто используется в сценариях, где анимация зависит от внешних факторов:

  • прокрутка страницы;
  • события интерфейса;
  • загрузка данных;
  • взаимодействие пользователя.

Пример привязки к прокрутке:

window.addEventListener('scroll', () => {
  const max = document.body.scrollHeight - window.innerHeight;
  const scroll = window.scrollY;

  const t = scroll / max;

  animation.map.forEach(item => {
    item.el.style.strokeDashoffset = item.length * (1 - t);
  });
});

Состояния и сброс анимации

Ручное управление требует явного возврата в исходное состояние:

function reset() {
  animation.map.forEach(item => {
    item.el.style.strokeDashoffset = item.length;
  });
}

Это позволяет многократно запускать анимацию без пересоздания SVG.


Типичные ошибки при использовании script-режима

Отсутствие нормализации прогресса

Без ограничения диапазона значения могут выходить за пределы допустимого интервала:

t = Math.min(Math.max(t, 0), 1);

Игнорирование длины пути

Разные SVG-пути имеют разные длины, поэтому применение фиксированных значений приводит к несогласованной анимации.


Частые прямые изменения DOM без оптимизации

Избыточные обновления style в каждом кадре могут приводить к просадке производительности при большом количестве путей.


Оптимизация обновлений

Для сложных SVG полезно кэшировать длины:

animation.map.forEach(item => {
  item.cachedLength = item.length;
});

И использовать их вместо повторных вычислений.

Также допустимо уменьшать частоту обновлений:

if (frameCount % 2 === 0) {
  // обновление состояния
}

Комбинирование с easing-функциями

Script-режим позволяет применять любые функции сглаживания:

function easeInOut(t) {
  return t < 0.5
    ? 2 * t * t
    : -1 + (4 - 2 * t) * t;
}

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

const eased = easeInOut(progress);

item.el.style.strokeDashoffset =
  item.length * (1 - eased);

Управление несколькими анимационными слоями

SVG может содержать группы элементов, которые требуют независимого управления:

const layers = animation.map.reduce((acc, item) => {
  const layer = item.group || 0;
  acc[layer] = acc[layer] || [];
  acc[layer].push(item);
  return acc;
}, {});

Далее каждый слой анимируется с разной логикой, например с задержкой или различной скоростью.


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

Script-режим поддерживает многократный запуск без пересоздания:

function play() {
  reset();
  animate();
}

Это особенно важно при создании интерфейсов с повторяющимися SVG-анимациями, где пересоздание DOM нежелательно.