Генерация SVG на лету

Работа с динамически создаваемыми SVG-элементами в связке с Vivus позволяет строить интерфейсы, где графика не хранится заранее в виде статичных файлов, а формируется в момент выполнения скрипта. Такой подход особенно важен для визуализаций данных, интерактивных схем, генеративной графики и UI-компонентов, которые зависят от состояния приложения.

SVG в браузере создаётся через пространство имён XML. В отличие от HTML-элементов, использование document.createElement здесь неприменимо — требуется createElementNS.

const SVG_NS = "http://www.w3.org/2000/svg";

const svg = document.createElementNS(SVG_NS, "svg");
svg.setAttribute("width", "400");
svg.setAttribute("height", "200");
svg.setAttribute("viewBox", "0 0 400 200");

document.body.appendChild(svg);

Ключевой момент — обязательное указание namespace. Без него элементы будут интерпретироваться как HTML и не отрисуются корректно.

Генерация графических примитивов

SVG строится из набора базовых элементов: path, circle, rect, line, polyline, polygon, text. Для анимации с Vivus критически важны именно path, поскольку библиотека работает с обводкой (stroke-dasharray / stroke-dashoffset).

Пример динамического создания пути:

const path = document.createElementNS(SVG_NS, "path");

const d = "M 50 100 C 150 0, 250 200, 350 100";

path.setAttribute("d", d);
path.setAttribute("fill", "none");
path.setAttribute("stroke", "black");
path.setAttribute("stroke-width", "2");

svg.appendChild(path);

Здесь кривая Безье создаётся программно, а не загружается из файла.

Генерация сложных форм алгоритмически

SVG особенно удобен для процедурной графики. Например, синусоидальная линия:

let d = "M 0 100";

for (let x = 0; x <= 400; x += 10) {
  const y = 100 + Math.sin(x * 0.05) * 40;
  d += ` L ${x} ${y}`;
}

const wave = document.createElementNS(SVG_NS, "path");
wave.setAttribute("d", d);
wave.setAttribute("fill", "none");
wave.setAttribute("stroke", "blue");

svg.appendChild(wave);

Такие пути идеально подходят для последующей анимации через Vivus, поскольку представляют собой непрерывную линию.

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

Vivus работает с обводкой путей, постепенно «рисуя» их на экране. Для корректной работы требуется соблюдение нескольких условий:

  • fill должен быть отключён или не мешать восприятию (fill="none")
  • элементы должны иметь stroke
  • желательно избегать сложных масок и фильтров
  • пути должны быть непрерывными

Дополнительно часто используется обнуление длины линии через CSS:

path {
  stroke-dasharray: 1000;
  stroke-dashoffset: 1000;
}

Однако Vivus обычно управляет этим автоматически.

Инициализация Vivus на динамически созданном SVG

После генерации SVG можно подключить анимацию:

const vivus = new Vivus(svg, {
  type: "delayed",
  duration: 200,
  animTimingFunction: Vivus.EASE
});

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

Проблема раннего запуска и решение

При динамической генерации часто возникает ситуация, когда SVG ещё не полностью построен в момент инициализации.

Решение — отложенный старт:

document.body.appendChild(svg);

requestAnimationFrame(() => {
  new Vivus(svg, {
    type: "oneByOne",
    duration: 150
  });
});

requestAnimationFrame гарантирует, что браузер завершит DOM-обновление перед запуском анимации.

Пересоздание SVG и повторная анимация

При изменении данных SVG может быть пересоздан полностью:

function rebuild() {
  svg.innerHTML = "";

  const path = document.createElementNS(SVG_NS, "path");
  path.setAttribute("d", "M 0 50 L 400 150");
  path.setAttribute("stroke", "black");
  path.setAttribute("fill", "none");

  svg.appendChild(path);

  new Vivus(svg, {
    type: "sync",
    duration: 100
  });
}

Каждое новое построение требует новой инициализации Vivus, так как внутренняя модель анимации не отслеживает динамические изменения структуры.

Генерация нескольких слоёв SVG

При сложных визуализациях SVG часто разбивается на группы <g>:

const group = document.createElementNS(SVG_NS, "g");

for (let i = 0; i < 5; i++) {
  const line = document.createElementNS(SVG_NS, "path");

  const y = 20 + i * 30;
  line.setAttribute("d", `M 0 ${y} L 400 ${y}`);
  line.setAttribute("stroke", "black");
  line.setAttribute("fill", "none");

  group.appendChild(line);
}

svg.appendChild(group);

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

Интеграция с данными (data-driven SVG)

SVG может строиться из массивов данных:

const data = [10, 40, 25, 60, 90];

const step = 80;
let d = "M 0 100";

data.forEach((value, index) => {
  const x = index * step;
  const y = 200 - value;
  d += ` L ${x} ${y}`;
});

const graph = document.createElementNS(SVG_NS, "path");
graph.setAttribute("d", d);
graph.setAttribute("stroke", "red");
graph.setAttribute("fill", "none");

svg.appendChild(graph);

Такой подход позволяет строить графики, диаграммы и временные ряды, которые затем плавно прорисовываются Vivus.

Управление скоростью и стилем прорисовки

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

  • delayed — последовательная прорисовка сегментов
  • sync — равномерная анимация всех путей
  • oneByOne — поэлементная анимация

В динамических SVG чаще используется sync, так как он лучше подходит для непрерывно создаваемых форм.

new Vivus(svg, {
  type: "sync",
  duration: 300
});

Оптимизация генерации SVG

При работе с большими сценами важны следующие принципы:

  • минимизация количества DOM-операций
  • использование DocumentFragment
  • предварительная сборка d-строк
  • избегание частых перерасчётов layout

Пример с фрагментом:

const fragment = document.createDocumentFragment();

for (let i = 0; i < 50; i++) {
  const path = document.createElementNS(SVG_NS, "path");
  path.setAttribute("d", `M 0 ${i * 5} L 400 ${i * 5}`);
  path.setAttribute("stroke", "black");
  path.setAttribute("fill", "none");

  fragment.appendChild(path);
}

svg.appendChild(fragment);

Пересчёт геометрии перед анимацией

При сложных трансформациях важно учитывать актуальную геометрию SVG:

const length = path.getTotalLength();
path.style.strokeDasharray = length;
path.style.strokeDashoffset = length;

Хотя Vivus делает это автоматически, ручной контроль используется при кастомных анимационных сценариях и нестандартных трансформациях путей.

Генеративные структуры и случайность

SVG удобно использовать для генеративной графики:

for (let i = 0; i < 20; i++) {
  const x1 = Math.random() * 400;
  const y1 = Math.random() * 200;
  const x2 = Math.random() * 400;
  const y2 = Math.random() * 200;

  const line = document.createElementNS(SVG_NS, "path");
  line.setAttribute("d", `M ${x1} ${y1} L ${x2} ${y2}`);
  line.setAttribute("stroke", "black");
  line.setAttribute("fill", "none");

  svg.appendChild(line);
}

Такие структуры создают основу для визуальных эффектов, где Vivus превращает случайные линии в последовательное «рисование».

Повторная инициализация после изменения структуры

При изменении SVG необходимо пересоздание экземпляра:

let instance;

function animate() {
  if (instance) {
    instance.stop().destroy();
  }

  instance = new Vivus(svg, {
    type: "oneByOne",
    duration: 120
  });
}

Это связано с тем, что библиотека фиксирует список путей при создании и не отслеживает их дальнейшие изменения.

Особенности работы с большим количеством путей

При сотнях элементов важно учитывать:

  • увеличение времени инициализации Vivus
  • нагрузку на stroke-анимацию
  • необходимость группировки элементов

Часто применяется сегментация:

  • отдельные SVG на логические блоки
  • последовательный запуск анимаций
  • ленивое добавление путей в DOM

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