SVG в вёрстке
Иконка, которая не мылится, красится из CSS и весит меньше килобайта. Разбираем SVG изнутри: viewBox, спрайты, currentColor.
Что ты научишься делать
- Прочитаешь SVG как обычную разметку и поймёшь, что в нём что
- Соберёшь иконочный спрайт и покрасишь иконки из CSS
- Выберешь способ вставки под задачу: инлайн, img или фон
#Это не картинка, это разметка
PNG и JPG — сетка пикселей. Увеличил вдвое — получил кашу. SVG устроен иначе: это текстовый файл с описанием фигур. Браузер каждый раз рисует его заново под нужный размер.
Отсюда всё остальное: чёткость на любом экране, вес в сотни байт вместо десятков килобайт, возможность покрасить фигуру из CSS и открыть файл в редакторе кода.
#viewBox — главное, что нужно понять
viewBox="0 0 24 24" означает: «внутри этой картинки система координат от 0 до 24 по обеим осям». Это не пиксели, а условные единицы.
А сколько места картинка займёт на странице, решают width, height или CSS. Одна и та же иконка с viewBox="0 0 24 24" может быть 16 или 160 пикселей — содержимое просто отмасштабируется.
Так не надо
<svg width="24" height="24">
<path d="M4 12h16"/>
</svg>Без viewBox координаты жёстко привязаны к пикселям. Задашь в CSS другой размер — линия останется на месте, а холст обрежется.
Так надо
<svg viewBox="0 0 24 24" width="24" height="24">
<path d="M4 12h16"/>
</svg>С viewBox содержимое масштабируется вместе с картинкой. Атрибуты размера при этом полезны: они резервируют место до загрузки.
#Три способа вставки
| Способ | Красится из CSS | Кэшируется | Когда брать |
|---|---|---|---|
| Инлайн в HTML | Да, полностью | Нет — едет с каждой страницей | Иконки, которые меняют цвет |
<img src="i.svg"> | Нет | Да, отдельным файлом | Иллюстрации и логотипы одного цвета |
background-image | Нет | Да | Декор, маркеры списков, фоны |
Спрайт + <use> | Да | Да, один файл на все иконки | Набор иконок в интерфейсе |
Главный водораздел — нужно ли красить иконку из CSS. Внутрь <img> и фона стили страницы не проникают: файл рисуется в изоляции.
#currentColor — почему иконки красятся сами
Ключевое слово currentColor означает «текущий цвет текста». Если в иконке написать stroke="currentColor", она унаследует цвет от родителя — и не нужно ни одного отдельного правила.
#Спрайт: один файл на все иконки
Инлайнить одну и ту же иконку двадцать раз — значит двадцать раз повторить её код. Спрайт решает это: все иконки описываются один раз внутри <symbol>, а в местах использования остаётся короткая ссылка.
<svg width="0" height="0" aria-hidden="true" style="position:absolute"> <symbol id="i-check" viewBox="0 0 24 24"> <path d="m5 12.5 4.5 4.5L19 7"/> </symbol> <symbol id="i-cross" viewBox="0 0 24 24"> <path d="m6 6 12 12M18 6 6 18"/> </symbol></svg><!-- Дальше по странице — только ссылки --><svg class="icon"><use href="#i-check"/></svg><svg class="icon"><use href="#i-cross"/></svg>viewBox задаётся у <symbol>, а не у места использования. Размер задаётся снаружи — обычно классом.
#Что выкинуть из экспорта
Файл, выгруженный из редактора, обычно вдвое-втрое больше нужного. Редактор кладёт в него служебные данные, которые браузеру не нужны.
| Что удалить | Почему |
|---|---|
<?xml ... ?> и <!DOCTYPE> | Нужны отдельному файлу, но не инлайновому SVG |
<title> вроде «Frame 12» | Имя слоя из редактора. Читалка озвучит его вслух |
xmlns:figma, data-name, id="Layer_1" | Служебные атрибуты редактора |
Атрибуты fill на каждом пути | Мешают покрасить иконку из CSS |
Группы <g> без трансформаций | Пустая вложенность |
Числа вида 12.000000001 | Двух знаков после запятой хватает с запасом |
#SVG в CSS без отдельного файла
Маленькую графику можно положить прямо в стили — как data-URI. Это экономит запрос и удобно для декоративных мелочей: галочки в списке, стрелки у выпадающего списка, узора на фоне.
Важная тонкость: в url() нужно экранировать решётку и кавычки, иначе браузер разберёт значение неправильно.
.checklist li { padding-inline-start: 28px; background-image: url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="%230f8f6a" stroke-width="2.5"><path d="m5 12.5 4.5 4.5L19 7"/></svg>'); background-repeat: no-repeat; background-size: 18px; background-position: 0 .2em;}#Доступность SVG
Иконки бывают двух видов, и обращаться с ними надо по-разному.
Декоративная — рядом есть текст, иконка просто дублирует смысл. Её прячут: aria-hidden="true".
Смысловая — иконка сама по себе несёт информацию, например кнопка-крестик без подписи. Ей нужно имя.
<!-- Рядом есть текст: иконку прячем от читалки --><button type="button"> <svg class="icon" aria-hidden="true"><use href="#i-check"/></svg> Сохранить</button><!-- Текста нет: имя даём кнопке, а не картинке --><button type="button" aria-label="Закрыть окно"> <svg class="icon" aria-hidden="true"><use href="#i-cross"/></svg></button><!-- Самостоятельная иллюстрация: роль и заголовок на самом svg --><svg role="img" aria-labelledby="chart-title" viewBox="0 0 200 120"> <title id="chart-title">Продажи выросли вдвое за квартал</title> <path d="M10 100 L60 70 L120 40 L190 20"/></svg>#Как это выглядит в реальном проекте
@layer components { .icon { /* Размер по шрифту рядом: иконка растёт вместе с текстом */ inline-size: 1.25em; block-size: 1.25em; flex: none; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; /* Оптическая посадка на строку: иконки кажутся выше текста */ vertical-align: -0.16em; } .icon--sm { inline-size: 1em; block-size: 1em; } .icon--lg { inline-size: 1.75em; block-size: 1.75em; } /* Заливочные иконки — отдельным модификатором */ .icon--solid { fill: currentColor; stroke: none; }}#Когда НЕ надо
- Фотографии. SVG описывает фигуры. Попытка сохранить в него фото даёт файл на несколько мегабайт — это делают только автоматические трассировщики, и результат всегда хуже WebP.
- Сложные иллюстрации инлайном. Карта страны с тысячей контуров в разметке раздувает HTML и тормозит первый рендер. Такое подключают через
img. - Иконочные шрифты «зато привычно». Иконочный шрифт ломается при отключённых шрифтах, требует псевдоэлементов и не даёт многоцветности. Спрайт лучше по всем пунктам.
- Инлайн одинаковых иконок. Двадцать копий одной иконки в разметке — это двадцать копий её кода. Для повторяющихся нужен спрайт.
Где спотыкаются почти все
Забыт viewBox
Почему Иконка перестаёт масштабироваться: при смене размера содержимое остаётся прежним, а холст обрезается.
Как надо Всегда указывай viewBox. Размер задавай отдельно, в CSS.
На путях остались атрибуты fill из редактора
Почему Атрибут в разметке перебивает наследование, и currentColor перестаёт работать: иконка остаётся чёрной.
Как надо Вычисти fill из путей и задай его один раз в CSS через currentColor.
Одинаковые id внутри нескольких инлайновых SVG
Почему Градиенты и маски ищутся по id на всей странице. Два id="grad" — и вторая иконка возьмёт градиент первой.
Как надо Делай id уникальными или используй спрайт, где каждая иконка описана один раз.
Название слоя из редактора осталось в title
Почему Программа чтения с экрана произнесёт «Frame 12» вместо осмысленного описания.
Как надо Удали служебный title. Декоративной иконке — aria-hidden="true".
- Векторная графика SVG
- Изображение, описанное фигурами и координатами, а не пикселями.
- Область просмотра viewBox
- Система координат внутри SVG. Не связана с размером на странице.
- Спрайт sprite
- Один файл с набором
symbol, на которые ссылаются черезuse. - Текущий цвет currentColor
- Ключевое слово, подставляющее значение
colorродителя. - Контур path
- Универсальная фигура, описанная командами в атрибуте
d.
Проверь себя
Это условные единицы. Размер на странице задают <code>width</code>, <code>height</code> или CSS.
Внутри <code>img</code> — отдельный документ. Красить из CSS можно только инлайновый SVG или спрайт через <code>use</code>.
Атрибут в разметке перебивает наследование. Вычисти <code>fill</code> и <code>stroke</code> из путей.
Текст уже всё сказал. Иконка дублирует смысл — её прячут, чтобы читалка не повторяла дважды.
SVG описывает фигуры. Фотография — это миллионы пикселей, ей нужен растровый формат вроде WebP.
Задание
Собрать иконочный спрайт
В разметке три раза повторяется один и тот же код иконки. Собери спрайт: опиши каждую иконку один раз внутри <symbol> с уникальным id, а в кнопках оставь <svg class="icon"><use href="#id"/></svg>.
В CSS задай иконкам stroke: currentColor, чтобы они красились вместе с текстом кнопки. Декоративным иконкам не забудь aria-hidden.
Подсказка 1
Спрайт кладётся один раз в начало: <svg width="0" height="0" style="position:absolute"><symbol id="i-check" viewBox="0 0 24 24">…</symbol></svg>
Подсказка 2
В кнопке остаётся: <svg class="icon" aria-hidden="true"><use href="#i-check"/></svg>
Подсказка 3
В CSS: .icon { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 2; }
Показать решение
<svg width="0" height="0" aria-hidden="true" style="position:absolute"> <symbol id="i-check" viewBox="0 0 24 24"> <path d="m5 12.5 4.5 4.5L19 7"/> </symbol> <symbol id="i-cross" viewBox="0 0 24 24"> <path d="m6 6 12 12M18 6 6 18"/> </symbol></svg><button class="btn btn--ok"> <svg class="icon" aria-hidden="true"><use href="#i-check"/></svg> Сохранить</button><button class="btn btn--bad"> <svg class="icon" aria-hidden="true"><use href="#i-cross"/></svg> Удалить</button>.icon { width: 18px; height: 18px; flex: none; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;}.btn { display: inline-flex; align-items: center; gap: 8px; padding: 9px 16px; font: inherit; font-weight: 600; background: #f4f4f8; border: 0; border-radius: 999px;}.btn--ok { color: #0f8f6a; }.btn--bad { color: #c8394a; }