Работа с анимацией в SVG часто упирается в задачу плавного
преобразования одной геометрии в другую. В контексте D3.js это означает
необходимость интерполяции строковых представлений, прежде всего
d-атрибута элементов <path>. Обычная
линейная интерполяция здесь неприменима: SVG-путь представляет собой
синтаксически сложную структуру, где важны команды, координаты, типы
сегментов и их согласованность.
Для решения этой задачи используется специализированный интерполятор
d3.interpolatePath, обеспечивающий морфинг одного пути в
другой с учётом геометрической структуры.
Стандартный пакет D3 включает общий механизм интерполяции значений
через d3-interpolate. Он покрывает числа, цвета, массивы,
объекты и частично строки через d3.interpolateString.
Однако SVG-пути требуют более строгого подхода.
d3.interpolatePath относится к отдельному модулю:
d3-interpolate-patht → stringУстановка:
npm install d3-interpolate-path
Подключение:
import { interpolatePath } from "d3-interpolate-path";
SVG-путь представляет собой последовательность команд:
Проблема морфинга заключается в том, что два пути могут:
d3.interpolatePath решает задачу через этапы:
Оба пути приводятся к единому внутреннему представлению:
Если пути различаются по структуре, выполняется:
Это критический этап, позволяющий обеспечить корректную интерполяцию.
После нормализации создаётся функция:
const interp = interpolatePath(pathA, pathB);
Эта функция принимает параметр t ∈ [0, 1] и возвращает
промежуточный SVG-путь.
import { interpolatePath } from "d3-interpolate-path";
const pathA = "M10,10 L100,10 L100,100 L10,100 Z";
const pathB = "M50,10 C90,20 90,80 50,90 C10,80 10,20 50,10 Z";
const interp = interpolatePath(pathA, pathB);
console.log(interp(0)); // исходный квадрат
console.log(interp(0.5)); // промежуточная форма
console.log(interp(1)); // конечная кривая
Интерполяция путей особенно полезна при переходах:
import * as d3 from "d3";
import { interpolatePath } from "d3-interpolate-path";
const svg = d3.select("svg");
const path = svg.append("path")
.attr("d", pathA)
.attr("fill", "steelblue");
path.transition()
.duration(2000)
.attrTween("d", () => interpolatePath(pathA, pathB));
Здесь attrTween использует интерполятор для генерации
промежуточных значений d.
d3.interpolateString выполняет посимвольную или числовую
интерполяцию внутри строки, но не понимает структуру SVG-пути.
Пример проблемы:
d3.interpolateString("M0,0L10,10", "M100,100L200,200");
Такой подход:
interpolatePath решает это на уровне геометрии, а не
текста.
Если структуры слишком различаются, алгоритм:
Однако существуют ограничения:
При интерполяции кривых:
Пример:
const a = "M0,50 C20,0 80,0 100,50";
const b = "M0,50 C20,100 80,100 100,50";
const f = interpolatePath(a, b);
Результат — симметричное «раскрытие» кривой.
Типовые сценарии:
При наличии составных фигур:
const interp = interpolatePath(pathGroupA, pathGroupB);
Важно, чтобы:
Основные затраты приходятся на:
После создания интерполятора:
t → path дешёвыйrequestAnimationFrameРекомендуется:
interpolatePath органично встраивается в систему
переходов:
selection.transition()attrTweend3.timerПример с таймером:
const interp = interpolatePath(a, b);
d3.timer((elapsed) => {
const t = Math.min(1, elapsed / 1000);
path.attr("d", interp(t));
if (t === 1) return true;
});
Если пути представляют разные топологии (например, круг и звезда), возможны:
Пути с противоположным направлением могут давать:
Решается предварительной нормализацией направления.
Множественные subpaths требуют:
Помимо d3.interpolatePath часто используется:
d3.interpolatePath занимает промежуточную позицию:
Интерполяция путей становится фундаментом для:
Механизм превращает SVG из статической разметки в динамическую геометрию, где форма становится непрерывной функцией времени.