SVG – одна из самых интересных технологий браузера. С его помощью можно делать массу полезных и интересных компонентов. Это неотъемлемая часть моего стека.
В этой статье я поделюсь основами, чтобы заложить прочный фундамент для дальнейшего развития. Я покажу вам, почему SVG так хорош, и поделюсь фишками, которыми вы сможете пользоваться прямо сейчас.
Для понимания этой статьи не требуется специальных знаний и опыта работы с SVG, но предполагается, что вы знакомы с основами HTML/CSS/JS.
Hello, SVG
SVG (от англ. scalable vector graphics – масшабируемая векторная графика) – по сути, формат изображений наряду с .jpg
или .gif
. SVG можно помещать в тег <img>
с помощью атрибута src
:
<img
alt="return to homepage"
src="/images/home.svg"
/>
Это работает – картинка появится на странице, но настоящая магия происходит, когда мы используем встроенные svg.
Большинство форматов изображений, например .jpg
– двоичные; если попытаться открыть их в текстовом редакторе, мы увидим кучу невнятного текста. SVG, напротив, задаётся с помощью синтаксиса XML, как и HTML! Вместо указания цветов R/G/B для каждого пикселя, SVG содержит набор инструкций по отрисовке, необходимых для визуализации изображения.
Магическим образом мы можем вставить сырой SVG прямо в HTML-документ:
<style>
body {
margin: 0;
padding: 0;
background: hsl(210deg 15% 6%);
color: white;
}
.wrapper {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 16px;
height: 100vh;
}
svg {
display: block;
outline: 1px solid hsl(210deg 15% 30%);
}
</style>
<div class="wrapper">
<p>
Check out this SVG:
</p>
<svg width="100" height="100">
<circle
fill="hotpink"
r="30"
cx="50"
cy="50"
/>
</svg>
</div>
Результат:

В HTML нам предоставлен набор элементов, которые связаны с документом: абзацы (<p>
), заголовки (<h1>
) и списки (<ol>
) — те же элементы, что и в Microsoft Word. SVG — это то же самое, но его элементы предназначены для иллюстраций, например <circle>
, <polygon>
и <path>
.
Самое интересное, что SVG-элементы – полноправные члены DOM (от англ. Document Object Model – объектная модель документа). Мы можем использовать CSS и JavaScript для выбора и изменения SVG-узлов, как если бы они были HTML-элементами.
Многие атрибуты SVG, такие как цвет круга (fill
) и радиус (r
), используются в качестве CSS-свойств. Это значит, что можно изменять их в CSS и даже использовать CSS-переходы для анимации!
Именно это делает SVG таким мощным инструментом. Это своего рода альтернативная версия HTML, ориентированная на изображения, и мы можем использовать знания CSS/JS, чтобы делать их динамичными.
Это может показаться немного нелогичным, но я часто создаю свои SVG-файлы в редакторе кода, а не использую программное обеспечение для дизайна, такое как Illustrator или Figma. Фоторедакторы могут экспортировать в SVG, но, как правило, они всё смешивают в один <path>
элемент. Это значительно усложняет анимацию отдельных элементов, что, на мой взгляд, сводит на нет самое крутое в SVG. На самом деле, выбор инструмента зависит от того, что мы делаем. За пределами определённой сложности гораздо практичнее делать SVG в специализированном ПО. Но для простых изображений, которые я показал в примерах выше, я думаю, проще писать код вручную.
Базовые фигуры
Как мы видели выше, SVG содержит собственный набор примитивов пользовательского интерфейса. Вместо <div>
и <button>
у нас есть фигуры типа <circle>
и <polygon>
. Давайте рассмотрим их.
Линии
Возможно, самая простая фигура <line>
:
<svg width="280" height="280">
<line
x1="80"
y1="80"
x2="200"
y2="200"
stroke="oklch(0.9 0.3 164)"
stroke-width="5"
/>
</svg>

Это такая простая вещь для SVG, но достаточно сложная задача для HTML. Единственный способ нарисовать диагональную линию в HTML — создать длинный тонкий узел DOM и повернуть его, что быстро превращается в сложную математическую задачу, если нужно, чтобы эта линия начиналась и заканчивалась в определённых местах.
В SVG линии рисовать сравнительно просто. Мы указываем начальную точку (x1
и y1
) и конечную точку (x2
и y2
), и получаем прямую линию между этими двумя точками!
По умолчанию <line>
элементы полностью невидимы. Чтобы нарисовать линию, нам нужно задать ей цвет с помощью атрибута stroke
. Я также использую stroke-width
, чтобы сделать линию толще. stroke
и stroke-width
– примеры презентационных атрибутов, в отличие от x1/y1/x2/y2, которые являются геометрическими атрибутами. Подробнее о презентационных атрибутах мы поговорим далее в этой статье.
Прямоугольники
Прямоугольники позиционируются по верхнему левому углу, указанному с помощью x
и y
. Это начальная точка отсчета, от которой высчитываются width
и height
:
<svg width="300" height="300">
<rect
x="60"
y="100"
width="180"
height="100"
fill="none"
stroke="oklch(0.9 0.3 164)"
stroke-width="5"
/>
</svg>

На первый взгляд это похоже на a <div>
со свойством border
, но есть несколько принципиальных отличий.
Во-первых, обводка рисуется по центру контура, а не внутри или снаружи. Посмотрите, что происходит при увеличении stroke-width
:
<svg width="300" height="300">
<rect
x="80"
y="100"
width="140"
height="100"
stroke-width="50"
stroke="green"
fill="none"
/>
</svg>

Это справедливо для всех фигур, а не только для <rect>
. И, к сожалению, это невозможно настроить: мы не можем указать, должна ли обводка конкретной фигуры располагаться внутри или снаружи.
Еще один интересный момент, на который стоит обратить внимание: посмотрите, что произойдет, если мы уменьшим либо width
, либо height
до 0. Вы могли бы ожидать, что он станет прямой линией, но вместо этого вся фигура исчезнет.
В спецификации SVG такие фигуры называются «вырожденными» (что, на мой взгляд, довольно грубо!). Если двумерная фигура, например, <rect>
существует только в одном измерении, она считается недопустимой и не отображается. Несколько лет назад поведение было нестабильным: некоторые браузеры всё ещё отображали одномерные фигуры, а другие — нет. К счастью, все современные браузеры теперь следуют этой спецификации.
Наконец, мы можем скруглить углы нашего прямоугольника, используя свойства rx
и ry
, аналогично border-radius
в HTML:
<svg width="340" height="340">
<rect
x="80"
y="100"
width="500"
height="500"
rx="100"
ry="50"
stroke="green"
stroke-width="5"
fill="none"
/>
</svg>

Окружности
Размер окружности определяется ее радиусом r
. Мы управляем положением окружности, указывая центр с помощью cx
и cy
:
<svg width="280" height="280">
<circle
cx="140"
cy="140"
r="70"
fill="none"
stroke="oklch(0.9 0.3 164)"
stroke-width="5"
/>
</svg>

Как и в случае с <rect>
, окружность полностью исчезнет, если установить радиус 0.
Эллипсы
<ellipse>
тоже самое, что и <circle>
, но мы можем выбирать разные значения для его горизонтального и вертикального радиусов. Это позволяет нам создавать овалы:
<svg width="300" height="300">
<ellipse
cx="150"
cy="150"
rx="75"
ry="50"
fill="none"
stroke="oklch(0.9 0.3 164)"
stroke-width="5"
/>
</svg>

Многоугольники
Элемент <polygon>
позволяет создавать фигуры подобные этой:
<svg width="300" height="300">
<polygon
points="
60,100
100,180
140,140
180,180
220,100
"
/>
</svg>

Атрибут points
принимает список точек X/Y; браузер нарисует линию между каждой точкой и от конечной точки обратно к первой.
Это показалось мне немного странным, когда я изучал SVG. В моём представлении термин «многоугольник» относится к чему-то очень конкретному: к фигурам с вращательной симметрией, таким как треугольники, квадраты, шестиугольники и восьмиугольники:

Оказывается, это правильные (равносторонние) многоугольники. Они являются подмножеством более широкого мира многоугольников.
Для создания правильных многоугольников нам понадобится тригонометрия. Это немного выходит за рамки этой статьи, но я размещу расчёты, если вам интересно узнать больше:
<style>
html {
display: grid;
place-content: center;
height: 100vh;
background: hsl(210deg 15% 6%);
}
svg {
outline: 1px solid hsl(210deg 15% 30%);
}
</style>
<svg class="parent-svg" width="280" height="280">
<polygon
class="mister-polygon"
stroke="goldenrod"
stroke-width="5"
fill="none"
/>
</svg>
<script>
const svg = document.querySelector('.parent-svg');
const polygon = document.querySelector('.mister-polygon');
// изменяйте эти значения:
const numOfSides = 8;
const radius = 80;
function drawPolygon() {
const svgWidth = Number(svg.getAttribute('width'));
const svgHeight = Number(svg.getAttribute('height'));
const cx = svgWidth / 2;
const cy = svgHeight / 2;
const points = range(numOfSides).map((index) => {
// Смещение вращения используется для того, чтобы четные
// многоугольники, такие как шестиугольники/восьмиугольники, были расположены плоской стороной вверх, а не острой.
// Установите это значение на «0», если вам это не нужно.
const rotationOffset = numOfSides % 2 === 0
? Math.PI / numOfSides
: 0;
const angle =
(index * 2 * Math.PI) / numOfSides -
Math.PI / 2 +
rotationOffset;
const x = cx + radius * Math.cos(angle);
const y = cy + radius * Math.sin(angle);
return `${x},${y}`;
});
polygon.setAttribute(
'points',
points.join(' ')
);
}
drawPolygon();
</script>

В приведенном выше коде я использовал запятые и переносы строк, чтобы сделать координаты точек читаемыми:
<polygon
points="
60,100
100,180
140,140
180,180
220,100
"
/>
Эти дополнительные символы считаются «лишними» в спецификации SVG. Они допустимы, но не нужны. Большинство SVG-изображений, которые вы встретите в реальной жизни, будут выглядеть примерно так:
<polygon points="60 100 100 180 140 140 180 180 220 100" />
Раньше подобная оптимизация могла дать небольшой выигрыш в производительности веб-сайтов. Однако сегодня веб-серверы используют сжатие gzip, а это значит, что несколько дополнительных запятых и переносов строк не окажут существенного влияния на размер файла.
Поэтому я настоятельно рекомендую вам добавлять удобное форматирование в ваши SVG-файлы! Пользователи этого не заметят, но другие разработчики в вашей команде (и вы сами в будущем) выиграют от читабельных SVG-файлов.
Есть ещё пара примитивных фигур, например, <polyline>
и <text>
, но, думаю, мы уже рассмотрели достаточно для введения. Давайте продолжим.
Масштабируемые SVG-изображения
До сих пор мы использовали «абсолютные» координаты. Это означает, что наши SVG-изображения должны иметь строго определённый размер, иначе всё сломается:
<svg width="180" height="220">
<circle
cx="150"
cy="110"
r="60"
stroke="var(--gold)"
stroke-width="10"
/>
</svg>

Окружность должна располагаться в центре элемента шириной 300 пикселей. Однако при уменьшении ширины элемента окружность не уменьшается, а обрезается. Большинство изображений работают иначе! При рендеринге .jpg
фотография будет масштабироваться вместе с размером элемента.
Одним из (не очень удачных) решений этой проблемы является динамический пересчет всех значений на основе ширины. Я выполняю вычисления в JavaScript, чтобы рассчитать геометрические и презентационные свойства, исходя из предполагаемой ширины в 300 пикселей. Таким образом, если ширина на самом деле 150 пикселей, все значения умножаются на 0,5.
Этот способ работает, но он очень сложный, даже для такой простой фигуры, как окружность выше. К счастью, есть гораздо лучший способ решить эту проблему.
Посмотрите:
<svg
width="165"
viewBox="0 0 300 220"
>
<circle
cx="150"
cy="110"
r="60"
stroke="var(--gold)"
stroke-width="10"
/>
</svg>

Атрибут viewBox
определяет внутреннюю систему координат. При его указании элементы <circle>
, <rect>
и <polygon>
перестанут наследовать необработанные значения пикселей DOM и вместо этого будут использовать эту внутреннюю систему координат.
Атрибут viewBox
принимает четыре числа, но на самом деле мы можем считать, что это две пары по два числа.
Первые два числа задают отображаемую часть SVG-изображения (по сути координата верхнего левого угла – прим. пер.)
<svg
width="300"
height="300"
viewBox="0 0 300 300"
>
<rect
x="0"
y="0"
width="200"
height="200"
/>
</svg>

Если мы изменим значение на viewBox="50 50 300 300"
, то увидим только часть квадрата, потому что поле просмотра сместилось на 50 вниз и вправо:

Поведение viewBox
напоминает таковое у атрибутов x
, y
прямоугольника <rect>
, только x
и y
задают начальную точку отсчета фигуры, а viewBox
точку отсчета поля просмотра. – прим. пер.
Интересное отличие между HTML-документами и SVG-изображениями заключается в том, что у SVG нет краёв. Теоретически они простираются во всех направлениях на бесконечность. Ничто не мешает нам разместить фигуру на расстоянии 1 000 000 пикселей от исходной точки в любом направлении. viewBox
решает, какую часть этого бесконечного поля мы смотрим.
Давайте поговорим о второй паре значений, используемых для viewBox
. Эти два значения позволяют нам указать ширину и высоту видимой области:
<svg
width="300"
height="300"
viewBox="0 0 200 200"
>
<rect
x="0"
y="0"
width="200"
height="200"
/>
</svg>

Если мы изменим значение на viewBox="0 0 550 550"
, то мы увеличим поле просмотра:

Размер SVG-изображения при этом не меняется — он управляется атрибутами width
/height
или CSS. Вместо этого фактически изменяется уровень масштабирования.
В примере выше наш SVG-файл имеет размер 300 на 300 пикселей. Если установить значение viewBox="0 0 300 300"
, то получим идеальное соотношение 1:1 между внутренней системой координат и стандартной системой координат DOM (в пикселях).
Но предположим, что мы устанавливаем значение viewBox="0 0 150 150"
. SVG по-прежнему имеет размер 300x300 пикселей, но теперь отображает только область размером 150x150 пикселей нашего бесконечного холста SVG. Это фактически увеличивает масштаб в 2 раза, удваивая размер фигур внутри нашего SVG.
По аналогии с viewport
(поскольку они действительно очень похожи), это эквивалентно использованию функции масштабирования браузера (Ctrl
+
) для увеличения до 200%. Это не меняет размер окна браузера, но увеличивает всё в области просмотра в 2 раза от исходного размера.
Итак, мы увидели, как атрибут viewBox
можно использовать для перемещения видимой области (изменяя первые два числа) или для увеличения/уменьшения масштаба (изменяя последние два числа).
Честно говоря, я не уверен, что когда-либо делал что-то подобное. Единственный реалистичный вариант использования сдвига и масштабирования viewBox
, который я могу себе представить, — это когда у вас есть гигантская диаграмма с большим количеством деталей, и вы хотите провести пользователей по ней, переходя из одного раздела в другой.
Я показал вам это, чтобы помочь понять, как работает viewBox
. На практике мы обычно сохраняем значения viewBox
статическими, чтобы изображение всегда отображалось одинаково, независимо от размера SVG-файла. Это позволяет нам использовать один и тот же SVG-файл разных размеров в разных контекстах.
В растровых форматах, таких как .png
/.jpg
, изображение состоит из цветных пикселей. Масштаб изображения, при котором эти пиксели становятся видимыми, ограничен. Однако в случае с векторными изображениями, изображение состоит из математических инструкций. Это означает, что мы можем увеличивать масштаб до любого значения, не теряя при этом резкости! Несмотря на то, что я пользуюсь SVG уже десять лет, я все еще нахожу это немного магическим: можно увеличивать изображение в 10 или 100 раз, а изображение при этом продолжает выглядеть четким. ?
Презентационные атрибуты
В SVG фигуры могут быть либо заполнены атрибутом fill
, либо обведены атрибутом stroke
, либо и то, и другое. Название атрибута fill
говорит само за себя, поэтому давайте сосредоточимся на обводках. Они чем-то похожи на HTML-рамки, но гораздо более функциональны.
Варианты обводки:
<style>
circle {
stroke: hsl(45deg 100% 50%);
stroke-width: 6px;
stroke-dasharray: 20, 14;
stroke-linecap: butt;
}
</style>
<svg viewBox="0 0 200 200">
<circle cx="100" cy="100" r="50" />
</svg>

Посмотреть другие варианты обводки
<style>
circle {
stroke: oklch(0.7 0.2 348);
stroke-width: 16px;
stroke-dasharray: 0, 15;
stroke-linecap: round;
}
</style>
<svg viewBox="0 0 200 200">
<circle cx="100" cy="100" r="50" />
</svg>

<style>
circle {
stroke: oklch(0.9 0.2 179);
stroke-width: 4px;
stroke-dasharray: 0, 10, 25, 10;
stroke-linecap: square;
}
</style>
<svg viewBox="0 0 200 200">
<circle cx="100" cy="100" r="50" />
</svg>

<style>
circle {
stroke: white;
stroke-width: 10px;
stroke-dasharray: 0, 20, 26, 60;
stroke-linecap: round;
}
</style>
<svg viewBox="0 0 200 200">
<circle cx="100" cy="100" r="50" />
</svg>

Мы управляем отображением штрихов с помощью нескольких stroke
свойств CSS. Мы также можем задать их как встроенные атрибуты (то есть, вместо настройки stroke-width: 5px
в CSS, мы можем задать их stroke-width="5"
в самом SVG).
Краткий обзор этих свойств:
stroke
— задаёт цвет обводки. По умолчаниюtransparent
.stroke-width
— задает ширину обводки в пикселях.stroke-dasharray
— задаёт ширину каждого сегмента и зазор между ними. Если мы передаём два значения (например,10, 20
), мы говорим, что хотим получить штрих шириной 10 пикселей с зазором между ними 20 пикселей. Мы даже можем задать повторяющийся узор, указав более двух значений.stroke-linecap
— определяет, как будет выглядеть каждый штрих. Если толщина штриха 0 пикселей, мы получим маленькие кружочки сround
, маленькие квадратики соsquare
, или вообще ничего при значении по умолчанию,butt
.
Анимированные штрихи
Итак, поскольку презентационные атрибуты SVG, такие как stroke-width
, на самом деле являются свойствами CSS, мы можем анимировать их, как и все остальное в CSS!
Например так можно плавно переключаться между различными стилями обводки, используя стандартные средства CSS:
circle {
transition:
stroke 1200ms,
stroke-width 900ms,
stroke-dasharray 1500ms,
stroke-linecap 1000ms;
}
Есть ещё одно свойство штриха, которое особенно полезно для анимации: stroke-dashoffset
. Это свойство позволяет перемещать штрихи по границе фигуры:
<style>
rect {
stroke: oklch(0.9 0.25 164);
stroke-width: 5;
stroke-dasharray: 10, 10;
stroke-dashoffset: 0;
/* поизменяйте значение этого свойства,
чтобы увидеть смещение штрихов */
}
</style>
С этим свойством можно делать всё, что угодно. Например, можно заставить штрихи бегать по границе фигуры, словно маленькие марафонцы:
@keyframes casinoLights {
from {
stroke-dashoffset: 0;
}
to {
stroke-dashoffset: 26;
}
}
rect {
stroke-dasharray: 0, 26;
animation:
casinoLights 400ms linear infinite;
}
Для достижения плавного эффекта установите значение stroke-dashoffset
, равное суммарной длине штриха и зазора. В противном случае вы заметите мерцание при зацикливании анимации, когда штрихи возвращаются к исходному смещению. Также поэкспериментируйте с разной длиной зазора, чтобы найти значение, которое будет хорошо повторяться с учетом периметра вашей фигуры.
Анимируя длину и смещение штриха, мы можем создать причудливый спиннер:
@keyframes grow {
from {
stroke-dasharray:
calc(var(--circumference) * 0.05),
var(--circumference);
}
to {
stroke-dasharray:
calc(var(--circumference) * 0.25),
var(--circumference);
}
}
@keyframes spin {
from {
stroke-dashoffset: 0;
}
to {
stroke-dashoffset:
calc(var(--circumference) * -0.95);
}
}
circle {
/* Рассчитайте длину окружности в JS
и примените ее как пользовательское свойство: */
--circumference: 572px;
animation:
grow 1200ms infinite alternate paused,
spin 2400ms infinite alternate paused;
}
Наконец, возможно, самый известный трюк — создание иллюзии рисунка SVG:
<style>
polygon {
stroke-dasharray: 763, 10000;
stroke-dashoffset: 763;
transition:
stroke-dashoffset 3000ms;
}
</style>
<svg viewBox="0 0 280 320">
<polygon points="..." />
</svg>
Хитрость в том, что у нас есть один штрих, длина которого равна периметру фигуры (в данном случае 763 пикселя), и огромный зазор между ними (1000 пикселей). Мы рисуем фигуру, сдвигая этот штрих на место, анимируя stroke-dashoffset
.
Как вычислить длину фигуры? Для вычисления можно использовать JavaScript:
const element = document.querySelector('polygon');
// ? Этот магический метод вычисляет длину фигуры:
const pathLength = element.getTotalLength();
element.style.strokeDasharray = `${pathLength}, 1000`;
Вместо заключения
Одна из важных тем, которую мы не затронули в этой публикации, — это <path>
элемент. Этот элемент позволяет объединять инструкции по рисованию, используя свой собственный DSL, что позволяет рисовать сложные фигуры с использованием кривых Безье и эллиптических дуг. В ближайшие недели я опубликую ещё одну статью об этом элементе.
Спасибо, что дочитали до конца! ? Все интерактивные материалы из примеров доступны по ссылке в оригинальной статье.
Комментарии (3)
kovserg
23.07.2025 10:56А вот пример не дружественного svg
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"> <defs> <g id="g0"><line x1="0" y1="0" x2="200" y2="200" stroke="red"/></g> <g id="g1"><use href="#g0"/><use href="#g0"/><use href="#g0"/><use href="#g0"/><use href="#g0"/><use href="#g0"/><use href="#g0"/><use href="#g0"/><use href="#g0"/><use href="#g0"/></g> <g id="g2"><use href="#g1"/><use href="#g1"/><use href="#g1"/><use href="#g1"/><use href="#g1"/><use href="#g1"/><use href="#g1"/><use href="#g1"/><use href="#g1"/><use href="#g1"/></g> <g id="g3"><use href="#g2"/><use href="#g2"/><use href="#g2"/><use href="#g2"/><use href="#g2"/><use href="#g2"/><use href="#g2"/><use href="#g2"/><use href="#g2"/><use href="#g2"/></g> <g id="g4"><use href="#g3"/><use href="#g3"/><use href="#g3"/><use href="#g3"/><use href="#g3"/><use href="#g3"/><use href="#g3"/><use href="#g3"/><use href="#g3"/><use href="#g3"/></g> <g id="g5"><use href="#g4"/><use href="#g4"/><use href="#g4"/><use href="#g4"/><use href="#g4"/><use href="#g4"/><use href="#g4"/><use href="#g4"/><use href="#g4"/><use href="#g4"/></g> <g id="g6"><use href="#g5"/><use href="#g5"/><use href="#g5"/><use href="#g5"/><use href="#g5"/><use href="#g5"/><use href="#g5"/><use href="#g5"/><use href="#g5"/><use href="#g5"/></g> <g id="g7"><use href="#g6"/><use href="#g6"/><use href="#g6"/><use href="#g6"/><use href="#g6"/><use href="#g6"/><use href="#g6"/><use href="#g6"/><use href="#g6"/><use href="#g6"/></g> <g id="g8"><use href="#g7"/><use href="#g7"/><use href="#g7"/><use href="#g7"/><use href="#g7"/><use href="#g7"/><use href="#g7"/><use href="#g7"/><use href="#g7"/><use href="#g7"/></g> <g id="g9"><use href="#g8"/><use href="#g8"/><use href="#g8"/><use href="#g8"/><use href="#g8"/><use href="#g8"/><use href="#g8"/><use href="#g8"/><use href="#g8"/><use href="#g8"/></g> </defs> <use href="#g9"/> </svg>
Gary_Ihar
Понравилось, спасибо
В конце вы описали кодом крутые примеры, но хотелось бы ссылки на песочницу или гифки. Я ленивый, простите )
austnv Автор
спасибо за положительный коллбек! вот ссылка на статью. к сожалению, отдельных ссылок на песочницу нет, т.к. интерактив нативно встроен в оригинальный сайт, поэтому побаловатся можно только там)