Firefox 137 представил SVG Path Data API — нативный способ читать, создавать и управлять SVG-путями программно в JavaScript. Изучите getPathData(), setPathData() и getPathSegmentAtLength() с реальными примерами кода.
Более двух десятилетий работа с SVG-путями в JavaScript означала одно: парсинг
и сборку строк. Каждый <path d="M10 10 L 20 20..."> требовал
разбиения атрибута d, токенизации команд, обработки неявных команд
и обратной сборки — всё через конкатенацию строк. Это было хрупко, чревато
ошибками и абсолютно непрозрачно для проверки типов.
SVG Path Data API (доступен в Firefox 137+,
редакторский черновик W3C от сентября 2025) полностью меняет это. API добавляет
три метода на SVGPathElement — getPathData(),
setPathData() и getPathSegmentAtLength() — которые
представляют данные SVG-пути как структурированные JavaScript-объекты вместо
непрозрачных строк.
В этом руководстве я расскажу всё, что нужно знать: поверхность API с полным синтаксисом, практические примеры кода, реальные сценарии использования, поддержка браузеров, стратегии полифиллов и типичные ошибки.
До Path Data API любое манипулирование SVG-путём включало работу с атрибутом
d — строкой вроде "M 10 10 L 20 20 L 30 10 Z".
Чтобы программно изменить путь, нужно было:
element.getAttribute('d')element.setAttribute('d', новаяСтрока)Любая ошибка в парсере — пропущенная неявная команда, неверное количество контрольных точек Безье или отсутствующий пробел перед отрицательным числом — могла незаметно сломать весь SVG. Отладка превращалась в разглядывание длинных строк чисел. Этот класс проблем должен был получить нативное API годы назад.
💡 Вывод: Строковое манипулирование SVG-путями чревато ошибками, сложно в отладке и совершенно непригодно для сложных операций, таких как морфинг анимации или интерактивные редакторы. Path Data API решает это структурированными типизированными данными.
SVG Path Data API добавляет три метода к SVGPathElement, каждый
из которых решает конкретную задачу в рабочем процессе управления путями:
| Метод | Назначение | Возвращает |
|---|---|---|
getPathData() |
Чтение всех сегментов пути как структурированных объектов | PathDataSegment[] |
setPathData(segments) |
Замена пути новым массивом сегментов | undefined |
getPathSegmentAtLength(distance) |
Определение сегмента на заданном расстоянии от начала пути | PathDataSegment | null |
Каждый сегмент, возвращаемый API, следует единой структуре:
interface PathDataSegment {
type: string; // Команда: 'M', 'L', 'C', 'Q', 'A', 'Z' и т.д.
values: number[]; // Координаты для этой команды
}
Свойство type использует те же одно- или двухсимвольные коды
команд, что и синтаксис строки SVG-пути: M (moveto),
L (lineto), C (кубическая кривая Безье),
Q (квадратичная кривая), A (дуга),
Z (закрытие пути). Строчные буквы означают относительные
координаты; заглавные — абсолютные.
Массив values содержит ровно столько координат, сколько нужно
для данного типа команды:
| Команда | Тип | Values | Описание |
|---|---|---|---|
| M, m | Moveto | [x, y] |
Перемещение в точку |
| L, l | Lineto | [x, y] |
Прямая линия |
| H, h | Горизонтальная линия | [x] |
Горизонтальная линия |
| V, v | Вертикальная линия | [y] |
Вертикальная линия |
| C, c | Кубическая Безье | [x1, y1, x2, y2, x, y] |
Две контрольные точки + конечная |
| S, s | Гладкая кубическая Безье | [x2, y2, x, y] |
Отражённая контрольная + конечная |
| Q, q | Квадратичная Безье | [x1, y1, x, y] |
Одна контрольная + конечная |
| T, t | Гладкая квадратичная Безье | [x, y] |
Отражённая контрольная + конечная |
| A, a | Дуга | [rx, ry, xAxisRot, largeArcFlag, sweepFlag, x, y] |
Эллиптическая дуга |
| Z, z | Закрытие пути | [] |
Закрытие текущего подпути |
Метод getPathData() — самый простой из трёх. Вызовите его на
любом элементе <path> и получите чистый массив объектов
сегментов:
const path = document.querySelector('svg path');
// Старый способ — хрупкий парсинг строки
const oldD = path.getAttribute('d');
// "M10 80 Q 52.5 10, 95 80 T 180 80 Z"
// Новый способ — структурированные данные
const segments = path.getPathData();
console.log(segments);
// [
// { type: 'M', values: [10, 80] },
// { type: 'Q', values: [52.5, 10, 95, 80] },
// { type: 'T', values: [180, 80] },
// { type: 'Z', values: [] }
// ]
Каждый сегмент — это чистый объект с явным типом и значениями. Больше не
нужно гадать, означает ли "10-20" [10, -20]
или [10, 20]. Больше не нужно писать собственный токенизатор
SVG-пути.
Со структурированными данными анализ пути тривиален:
function analyzePath(pathElement) {
const segments = pathElement.getPathData();
return {
totalSegments: segments.length,
commandTypes: segments.reduce((acc, s) => {
acc[s.type] = (acc[s.type] || 0) + 1;
return acc;
}, {}),
boundingBox: calculateBoundingBox(segments),
isClosed: segments[segments.length - 1]?.type === 'Z'
};
}
function calculateBoundingBox(segments) {
let minX = Infinity, minY = Infinity;
let maxX = -Infinity, maxY = -Infinity;
for (const seg of segments) {
const { type, values } = seg;
if (type === 'Z' || type === 'z') continue;
const endIdx = getEndCoordinateIndex(type);
if (endIdx >= 0) {
const x = values[endIdx - 1];
const y = values[endIdx];
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
}
}
return { minX, minY, maxX, maxY, width: maxX - minX, height: maxY - minY };
}
function getEndCoordinateIndex(type) {
const ends = { M: 2, L: 2, H: 1, V: 1, C: 6, S: 4, Q: 4, T: 2, A: 6 };
return ends[type.toUpperCase()] - 1;
}
Раньше для извлечения информации о границах из SVG-путей требовался парсинг
строки d с помощью регулярных выражений — и каждое регулярное
выражение было хрупким приближением. Теперь это просто обход массива.
Настоящая сила API проявляется в setPathData(). Вместо сборки
строки вы передаёте массив объектов PathDataSegment:
const path = document.querySelector('svg path');
// Читаем существующий путь
const segments = path.getPathData();
// Изменяем второй сегмент — перемещаем его конечную точку
segments[1] = {
type: 'L',
values: [150, 200]
};
// Записываем обратно — конкатенация строк не нужна
path.setPathData(segments);
Это значительно безопаснее строковых манипуляций. Каждый сегмент — изолированный объект. Изменение одного сегмента не может случайно повредить остальной путь, в отличие от конкатенации строк, где пропущенный пробел или лишний символ ломает всё.
Вы также можете строить пути целиком на JavaScript, не прикасаясь к строкам:
function createStarPath(cx, cy, outerR, innerR, points) {
const segments = [];
const step = Math.PI / points;
for (let i = 0; i < points * 2; i++) {
const r = i % 2 === 0 ? outerR : innerR;
const angle = i * step - Math.PI / 2;
const x = cx + r * Math.cos(angle);
const y = cy + r * Math.sin(angle);
if (i === 0) {
segments.push({ type: 'M', values: [x, y] });
} else {
segments.push({ type: 'L', values: [x, y] });
}
}
segments.push({ type: 'Z', values: [] });
return segments;
}
// Создаём пятиконечную звезду
const starSegments = createStarPath(100, 100, 80, 40, 5);
pathElement.setPathData(starSegments);
Эта функция генерирует идеально масштабированную звезду, используя только структурированные данные. Никакого построения строк, экранирования или граничных случаев с отрицательными числами — только чистая арифметика и конструирование объектов.
Частая задача — масштабирование или смещение координат пути. Со старым API нужно было парсить строку, извлекать координаты, изменять их и собирать строку заново. С Path Data API:
function scalePath(pathElement, scaleX, scaleY) {
const segments = pathElement.getPathData();
for (const seg of segments) {
if (seg.type === 'Z' || seg.type === 'z') continue;
for (let i = 0; i < seg.values.length; i++) {
if (i % 2 === 0) {
seg.values[i] *= scaleX; // X координата
} else {
seg.values[i] *= scaleY; // Y координата
}
}
}
pathElement.setPathData(segments);
}
// Масштабируем путь в 1.5 раза по горизонтали
scalePath(myPath, 1.5, 1);
Один из самых интересных сценариев — морфинг пути: плавная анимация перехода от одной формы к другой. Path Data API делает это значительно проще, потому что исходная и целевая формы — массивы структурированных объектов одного формата:
function morphPath(pathElement, targetSegments, duration = 1000) {
const startSegments = pathElement.getPathData();
if (startSegments.length !== targetSegments.length) {
console.warn('Количество сегментов не совпадает — морфинг может выглядеть некорректно');
}
const startTime = performance.now();
function animate(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
const eased = easeInOutCubic(progress);
const currentSegments = startSegments.map((start, i) => {
if (i >= targetSegments.length) return start;
const target = targetSegments[i];
const values = start.values.map((v, j) => {
const targetVal = target.values[j] ?? v;
return v + (targetVal - v) * eased;
});
return { type: target.type, values };
});
pathElement.setPathData(currentSegments);
if (progress < 1) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
}
function easeInOutCubic(t) {
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
}
// Пример: морфинг квадрата в круг
const square = [
{ type: 'M', values: [50, 50] },
{ type: 'L', values: [150, 50] },
{ type: 'L', values: [150, 150] },
{ type: 'L', values: [50, 150] },
{ type: 'Z', values: [] }
];
const circle = [
{ type: 'M', values: [100, 50] },
{ type: 'C', values: [127.6, 50, 150, 72.4, 150, 100] },
{ type: 'C', values: [150, 127.6, 127.6, 150, 100, 150] },
{ type: 'C', values: [72.4, 150, 50, 127.6, 50, 100] },
{ type: 'C', values: [50, 72.4, 72.4, 50, 100, 50] },
{ type: 'Z', values: [] }
];
morphPath(myPath, circle, 2000);
💡 Ключевая идея: Для плавного морфинга оба пути должны иметь одинаковое количество сегментов и одинаковые типы команд на соответствующих позициях. Если это невозможно, добавьте промежуточные шаги или нормализуйте сегменты перед морфингом.
Третий метод, getPathSegmentAtLength(distance), отвечает на
вопрос, который раньше требовал сложных геометрических библиотек: «Какой
сегмент пути находится на данном расстоянии от начала?»
const path = document.querySelector('svg path');
const totalLength = path.getTotalLength();
// Находим сегмент на 50% длины пути
const midpoint = path.getPathSegmentAtLength(totalLength / 2);
console.log(midpoint);
// { type: 'C', values: [x1, y1, x2, y2, x, y] }
Это незаменимо для:
Практический пример — отрисовка пути сегмент за сегментом:
async function progressiveDraw(pathElement, durationPerSegment = 300) {
const segments = pathElement.getPathData();
const partialSegments = [segments[0]];
pathElement.setPathData(partialSegments);
for (let i = 1; i < segments.length; i++) {
if (segments[i].type === 'Z' || segments[i].type === 'z') {
partialSegments.push(segments[i]);
pathElement.setPathData(partialSegments);
break;
}
partialSegments.push(segments[i]);
pathElement.setPathData(partialSegments);
await new Promise(r => setTimeout(r, durationPerSegment));
}
}
Path Data API — естественный выбор для программного построения графиков и визуализации данных. Вот простой генератор линейного графика:
function createLineChart(dataPoints, width, height, padding = 20) {
if (dataPoints.length < 2) return [];
const xScale = (width - 2 * padding) / (dataPoints.length - 1);
const yMin = Math.min(...dataPoints);
const yMax = Math.max(...dataPoints);
const yRange = yMax - yMin || 1;
const segments = [];
for (let i = 0; i < dataPoints.length; i++) {
const x = padding + i * xScale;
const y = height - padding - ((dataPoints[i] - yMin) / yRange) * (height - 2 * padding);
if (i === 0) {
segments.push({ type: 'M', values: [x, y] });
} else {
const prevX = padding + (i - 1) * xScale;
const prevY = height - padding - ((dataPoints[i - 1] - yMin) / yRange) * (height - 2 * padding);
const cpx1 = prevX + xScale * 0.5;
const cpx2 = x - xScale * 0.5;
segments.push({
type: 'C',
values: [cpx1, prevY, cpx2, y, x, y]
});
}
}
return segments;
}
// Использование
const data = [10, 45, 30, 70, 55, 90, 85];
const chartSegments = createLineChart(data, 400, 200);
chartPath.setPathData(chartSegments);
Самый амбициозный сценарий — интерактивный редактор SVG-путей. С
помощью getPathData() и setPathData() можно
собрать редактор с перетаскиванием точек в удивительно малом количестве строк:
class PathEditor {
constructor(pathElement) {
this.path = pathElement;
this.segments = pathElement.getPathData();
this.dragging = null;
this.bindEvents();
}
getPointPositions() {
const points = [];
for (const seg of this.segments) {
if (seg.type === 'Z' || seg.type === 'z') continue;
const endIdx = [6, 4, 2].find(i => seg.values.length >= i) ?? 2;
if (endIdx >= 2) {
points.push({
segIndex: this.segments.indexOf(seg),
valIndexX: endIdx - 2,
valIndexY: endIdx - 1,
x: seg.values[endIdx - 2],
y: seg.values[endIdx - 1]
});
}
}
return points;
}
bindEvents() {
const svg = this.path.closest('svg');
svg.addEventListener('mousedown', (e) => {
const rect = svg.getBoundingClientRect();
const mx = e.clientX - rect.left;
const my = e.clientY - rect.top;
const points = this.getPointPositions();
let minDist = Infinity;
for (const p of points) {
const dist = Math.hypot(p.x - mx, p.y - my);
if (dist < minDist && dist < 10) {
minDist = dist;
this.dragging = p;
}
}
});
svg.addEventListener('mousemove', (e) => {
if (!this.dragging) return;
const rect = svg.getBoundingClientRect();
const seg = this.segments[this.dragging.segIndex];
if (seg) {
seg.values[this.dragging.valIndexX] = e.clientX - rect.left;
seg.values[this.dragging.valIndexY] = e.clientY - rect.top;
this.path.setPathData(this.segments);
}
});
svg.addEventListener('mouseup', () => {
this.dragging = null;
});
}
}
// Использование
const editor = new PathEditor(document.querySelector('svg path'));
Это полнофункциональный редактор путей менее чем в 60 строках. Ключевая
идея в том, что setPathData() принимает тот же формат, который
возвращает getPathData() — поэтому обработчик перетаскивания
просто изменяет массив values и записывает его обратно.
По состоянию на июнь 2026 года SVG Path Data API имеет ограниченную поддержку браузеров:
Всегда проверяйте поддержку перед использованием API:
function supportsPathDataAPI() {
const path = document.createElementNS(
'http://www.w3.org/2000/svg', 'path'
);
return 'getPathData' in path;
}
if (supportsPathDataAPI()) {
const segments = myPath.getPathData();
} else {
const dString = myPath.getAttribute('d');
const segments = parsePathString(dString);
}
Для production используйте полифилл, оборачивающий старый строковый API:
function polyfillPathData() {
if (supportsPathDataAPI()) return;
SVGPathElement.prototype.getPathData = function() {
return parseDString(this.getAttribute('d') || '');
};
SVGPathElement.prototype.setPathData = function(segments) {
this.setAttribute('d', segmentsToDString(segments));
};
}
function segmentsToDString(segments) {
return segments.map(s => {
if (s.type === 'Z' || s.type === 'z') return 'Z';
return s.type + ' ' + s.values.join(' ');
}).join(' ');
}
polyfillPathData();
⚠️ Для Production: Полифилл выше упрощён для иллюстрации. Production-полифилл должен обрабатывать неявные повторяющиеся команды (например, "M 10 10 L 20 20 30 30"), флаги дуг и научную нотацию. Рассмотрите использование community-полифилла для продакшна.
setPathData() запускает полный ререндер пути. Для анимации
с 60fps это нормально. Для путей с тысячами сегментов проверьте
производительность.setPathData() не проверяет
корректность значений. NaN, Infinity или отрицательные радиусы дуг могут
сделать путь невидимым.SVG Path Data API — давно назревшее дополнение веб-платформы, которое наконец приносит структурированную работу с данными в SVG-пути. Он заменяет два десятилетия хрупких обходных путей парсинга строк чистыми типизированными JavaScript-объектами.
Ключевые выводы:
getPathData() читает сегменты пути как объекты с type и valuessetPathData() записывает данные пути из массива таких
объектов — без строковых манипуляцийgetPathSegmentAtLength() возвращает сегмент на заданном
расстоянии вдоль путиНачинайте экспериментировать с SVG Path Data API сегодня. Даже с текущими ограничениями браузеров API достаточно стабилен для экспериментальных проектов, а полифиллы хорошо работают в production, пока другие браузеры догоняют.
Хотите построить интерактивные SVG-визуализации? Я занимаюсь фронтенд-архитектурой, разработкой на SVG/Canvas и визуализацией данных. Посмотрите мои услуги или свяжитесь со мной для консультации.
Я создаю веб-приложения с использованием SVG, Canvas и современного JavaScript. Расскажите о задаче — бесплатная консультация.