В Vivus Vivus режим script отличается тем, что полностью
передаёт управление анимацией внешнему коду. В отличие от автоматических
стратегий отрисовки (delayed, sync,
oneByOne), здесь библиотека не выполняет интерполяцию сама,
а лишь подготавливает SVG-элементы и предоставляет инструменты для
ручного контроля прогресса.
Основная идея режима заключается в том, что разработчик
самостоятельно определяет момент, скорость и логику изменения параметров
отрисовки, обычно через stroke-dasharray и
stroke-dashoffset. Vivus в этом сценарии выступает как слой
инициализации и утилита для работы с длинами путей.
При использовании type: 'script' библиотека:
stroke-dasharray;stroke-dashoffset внешнему
коду;Таким образом, весь процесс анимации переносится в пользовательский цикл обновления.
Создание экземпляра не отличается от других режимов:
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;
Экземпляр 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, поскольку 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.
Без ограничения диапазона значения могут выходить за пределы допустимого интервала:
t = Math.min(Math.max(t, 0), 1);
Разные SVG-пути имеют разные длины, поэтому применение фиксированных значений приводит к несогласованной анимации.
Избыточные обновления style в каждом кадре могут
приводить к просадке производительности при большом количестве
путей.
Для сложных SVG полезно кэшировать длины:
animation.map.forEach(item => {
item.cachedLength = item.length;
});
И использовать их вместо повторных вычислений.
Также допустимо уменьшать частоту обновлений:
if (frameCount % 2 === 0) {
// обновление состояния
}
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 нежелательно.