SVG (Scalable Vector Graphics) — это векторный формат изображений, который одинаково чётко выглядит на любом экране и при любом масштабе. Именно поэтому SVG всё чаще вытесняет растровые изображения (например, PNG и GIF) в современных интерфейсах: иконки, логотипы, декоративные элементы — всё это сегодня принято делать в SVG.
Способов добавить SVG на страницу существует несколько, и у каждого свои плюсы и минусы. В этой статье я хочу рассказать о том способе, которым пользуюсь сам и который кажется мне наиболее удобным и универсальным в большинстве случаев. Но сначала сформулируем проблему — потому что без понимания того, что именно нас не устраивает в других методах, преимущества выбранного подхода не будут очевидны.
Что мы хотим от иконок
Иконки в современном интерфейсе — это не просто картинки. Они живут внутри кнопок, меняют цвет при наведении вместе с текстом, масштабируются вслед за шрифтом, появляются перед пунктами меню без лишней разметки. С точки зрения разработчика к идеальному решению можно предъявить несколько требований.
Управление цветом через CSS. Иконка в тексте, ссылке или кнопке должна автоматически подстраиваться под цвет окружающего контента и изменяться вместе с ним (например, становиться белой, когда кнопка меняет цвет при наведении) — без необходимости создавать отдельные файлы для каждого варианта цвета или написания сложного CSS или JavaScript кода.
Масштабирование вместе с текстом. Должна быть возможность задать размер иконки в em, чтобы она могла подстраиваться под font-size родителя. Это удобно: один CSS-класс работает везде, а размер регулируется контекстом.
Работа в псевдоэлементах. Часто нужно добавить иконку к элементу без изменения разметки — только через CSS, с помощью ::before или ::after. Это особенно важно, когда HTML генерируется CMS или сторонним кодом, который нельзя трогать. При этом желательно, чтобы метод вставки иконок в псевдоэлементы был тем же самым, что и для обычных иконок — единый подход проще поддерживать и проще объяснять в команде.
Производительность и кэширование. Файлы иконок должны кэшироваться браузером как обычные ресурсы. Код иконки не должен дублироваться при каждом ее использовании на странице.
Простота использования. Добавление новой иконки не должно требовать пересборки шрифта, перегенерации спрайта или настройки сложных инструментов. В идеале — положил SVG-файл в папку, написал одну строчку CSS, готово.
Ни один из существующих методов не закрывает все эти потребности одновременно. Но один из них покрывает их лучше остальных — и именно о нём пойдёт речь во второй половине статьи. Но сначала разберем какие варианты вставки SVG изображений применяются чаще всего сегодня.
Основные методы вставки SVG
Тег <img>
<img src="icon.svg" alt="иконка">
Самый очевидный способ. SVG загружается как внешний файл, браузер кэширует его, повторные обращения идут из кэша. Размер легко задаётся через CSS, в том числе в em.
Проблема в том, что SVG здесь — это изолированная картинка. Никакие CSS-правила страницы не проникают внутрь файла. Нельзя изменить цвет иконки через fill или stroke, нельзя заставить её наследовать цвет текста. Единственный обходной путь — CSS-фильтры (hue-rotate, saturate и прочее), но это грубый инструмент, дающий лишь приближённый результат.
В псевдоэлементах <img> также не используется. Технически можно написать content: url('icon.svg'), но с существенным ограничением: ширина и высота псевдоэлемента при этом игнорируются, размер определяется только атрибутами самого SVG-файла — контролировать его через CSS невозможно.
С точки зрения простоты — метод элементарный, никаких дополнительных инструментов не нужно. Но функциональные ограничения сужают его область применения до статичных изображений с фиксированными цветами: логотипы, иллюстрации, декоративная графика. Для интерфейсных иконок он не подходит.
CSS background-image
.icon {
background-image: url('icon.svg');
background-size: contain;
width: 2em;
height: 2em;
}
По сравнению с <img> появляется одно важное преимущество: метод работает в псевдоэлементах ::before и ::after. Можно добавить иконку к элементу чисто через CSS, не трогая разметку, и размер при этом нормально управляется через width/height.
Но с цветом — та же история, что у <img>. SVG-файл изолирован, currentColor не работает, для каждого цветового варианта нужен отдельный файл. Доступность тоже страдает: иконка невидима для скринридеров, задать alt нельзя.
Метод прост в использовании, но его применение в псевдоэлементах и в обычных элементах требует разного синтаксиса — что уже нарушает принцип единого подхода. А отсутствие управления цветом делает его пригодным только для декоративных элементов с фиксированной палитрой.
Inline SVG
<svg class="icon" viewBox="0 0 24 24" fill="currentColor">
<path d="..."/>
</svg>
Это метод максимального контроля. SVG становится частью DOM страницы, поэтому работают любые CSS-правила: fill, stroke, градиенты, анимации отдельных элементов. currentColor наследует цвет текста родителя автоматически. Если задать width: 1em; height: 1em, иконка масштабируется вместе со шрифтом.
Платой за контроль является всё остальное. Код SVG встраивается прямо в HTML — при использовании одной иконки на 50 страницах её код повторяется 50 раз. Разметка разбухает, кэшировать иконки отдельно от HTML невозможно. Inline SVG нельзя вставить в псевдоэлемент — ::before и ::after не поддерживают HTML-теги. Наконец, с точки зрения удобства разработки это самый трудоёмкий вариант: SVG-код иконки нужно каждый раз вставлять вручную или настраивать автоматизацию через сборщик.
Незаменим для многоцветных иконок, сложных иллюстраций и анимированной графики. Для массового использования монохромных интерфейсных иконок — избыточен.
SVG-спрайт и элемент <use>
<!-- Определение один раз: -->
<svg style="display:none">
<symbol id="icon-home" viewBox="0 0 24 24">...</symbol>
</svg>
<!-- Использование сколько угодно раз: -->
<svg class="icon"><use href="#icon-home"></use></svg>
Важно: старый атрибут xlink:href устарел и удалён из SVG 2.0. Используйте просто href.
Спрайт решает проблему дублирования: символ определяется один раз, а <use> клонирует его сколько угодно раз без повторения кода. Если вынести спрайт в отдельный файл, он будет кэшироваться браузером. currentColor работает — цвет наследуется через Shadow DOM.
Тонкость в том, что <use> создаёт Shadow DOM, и прямая стилизация вложенных элементов через CSS (svg use path { fill: red }) не работает. Цветом можно управлять только через color/fill на обёртке <svg> или через CSS-переменные. Градиенты из внешнего файла спрайта могут работать нестабильно. В псевдоэлементах метод не применяется вовсе.
С точки зрения простоты использования — этот метод так же значительно сложнее, того же <img>. Нужно заранее собрать спрайт-файл, поддерживать его актуальность при добавлении новых иконок, следить за уникальностью идентификаторов. Хороший выбор для больших проектов с устоявшимся набором иконок, но требует дополнительной инфраструктуры.
Иконочные шрифты
@font-face {
font-family: 'icons';
src: url('icons.woff2') format('woff2');
font-display: block;
}
.icon::before {
font-family: 'icons';
content: "\e001";
}
Иконка здесь — это текстовый символ в Unicode Private Use Area. Браузер рендерит её через движок шрифтов, а не SVG-рендерер. Отсюда и особенности.
Плюсы весомые: цвет управляется через обычное свойство color и наследуется от родителя автоматически. Весь набор иконок — один .woff2-файл, который кэшируется. Иконки нативно живут в псевдоэлементах — это их родная среда.
Минусы тоже существенные. Иконочный шрифт принципиально монохромен: градиенты возможны только через хак с background-clip: text, дающий ограниченный результат. Если пользователь переопределяет системный шрифт (часто для удобочитаемости), иконки превращаются в пустые квадраты. В режиме высокой контрастности Windows поведение непредсказуемо. Скринридеры могут прочитать Unicode-символ вслух — нужно явно добавлять aria-hidden="true". До загрузки шрифта возникает FOIT — иконки мигают или исчезают.
Главный практический вопрос — откуда взять шрифт. Готовые библиотеки (Font Awesome 6, Bootstrap Icons) весят 300–400 КБ без subsetting: при использовании 10 иконок вы всё равно грузите полторы тысячи. Для кастомного набора нужно собирать собственный шрифт через IcoMoon, fontello или npm-пакеты типа fantasticon — это отдельный процесс с настройкой и поддержкой. Добавить новую иконку уже не так просто, как положить файл в папку.
<object>, <iframe>, <embed>, data URI
Для полноты картины упомянем еще несколько методов, которые являются скорее экзотикой, нежели реально работающими способами добавления SVG изображений на страницу.
<object> и <embed> загружают SVG как внешний документ с собственным DOM. Теоретически к нему можно получить доступ через JavaScript (contentDocument), но CSS страницы туда не проникает — ни currentColor, ни пользовательские свойства. Используется редко, в основном когда нужен JS-доступ к SVG без инлайна.
<iframe> создаёт полностью изолированный контекст просмотра. Для иконок бессмысленен — избыточные накладные расходы при нулевых преимуществах.
Data URI — встраивание SVG-кода прямо в значение src или background-image в виде строки (data:image/svg+xml,...). Когда data URI находится в CSS-файле, он кэшируется вместе с ним; когда встроен в HTML — не кэшируется и дублируется при каждом запросе. В обоих случаях CSS страницы не имеет доступа к содержимому SVG, а CSS-переменные внутри строки data URI браузером не интерпретируются.
Сводная таблица
Перед тем как перейти к рассмотрению ещё одного метода, взглянем на картину целиком.
| Метод | Цвет через CSS | Градиент | Наследование |
Псевдоэлементы | Кэширование | Без дублирования | Многоцветность | Простота использования |
|---|---|---|---|---|---|---|---|---|
<img> |
✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ (внутри файла) |
★★★ |
background-image |
✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ (внутри файла) |
★★☆ |
| Inline SVG | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✓ | ★☆☆ |
SVG-спрайт + <use> |
~ | ~ | ✓ | ✗ | ✓ | ✓ | ✓ | ★★☆ |
| Иконочный шрифт | ✓ | ~ (хак) | ✓ | ✓ | ✓ | ✓ | ✗ | ★★☆ |
Глядя на таблицу, иконочный шрифт — единственный метод, который закрывает большинство потребностей одновременно: управление цветом, наследование currentColor, псевдоэлементы, кэширование, отсутствие дублирования. Именно поэтому он долгое время был золотым стандартом для интерфейсных иконок.
Однако у него есть два принципиальных ограничения, которые сложно игнорировать. Первое — монохромность: градиенты недоступны, а многоцветные иконки невозможны в принципе. Второе — сложность работы с кастомным набором: добавление новой иконки требует пересборки шрифтового файла, что превращает простую операцию в отдельный инфраструктурный процесс. Именно этот зазор — между удобством использования и неудобством поддержки — и заполняет метод, о котором пойдёт речь дальше.
Метод CSS mask-image: как это работает
Идея проста: пустой элемент получает цветной фон, а SVG-файл применяется как маска, определяющая, какие части этого фона останутся видимыми. Непрозрачные области SVG (форма иконки) пропускают фон, прозрачные — скрывают его. В результате мы видим цвет фона через трафарет формы иконки.
Базовые стили
Выносим общие свойства в один класс, а ссылку на конкретный SVG-файл — в отдельный модификатор:
.icon {
display: inline-block;
width: 1em;
height: 1em;
background-color: currentColor;
mask-size: contain;
mask-repeat: no-repeat;
mask-position: center;
/* Префикс для Safari */
-webkit-mask-size: contain;
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: center;
vertical-align: middle;
}
.icon-home { mask-image: url('home.svg'); -webkit-mask-image: url('home.svg'); }
.icon-user { mask-image: url('user.svg'); -webkit-mask-image: url('user.svg'); }
.icon-search { mask-image: url('search.svg'); -webkit-mask-image: url('search.svg'); }
Использование в HTML минималистично:
<p>Нажмите <i class="icon icon-home" aria-hidden="true"></i>, чтобы вернуться на главную</p>
<button>
<i class="icon icon-user" aria-hidden="true"></i>
Профиль
</button>
Использование background-color: currentColor гарантирует, что цвет иконки будет совпадать с цветом текста родительского элемента, а задание ширины и высоты иконки в 1em — означает, что она автоматически подстраивается под font-size родителя. Иконка, находящаяся в блоке с font-size: 14px и color: red будет красного цвета размером 14×14 пикселей. Если font-size: увеличится до 20px, иконка тоже станет 20×20px. Никаких дополнительных правил, никаких исключений.
Управление цветом
Поскольку цвет иконки — это обычный background-color, к нему применимо всё, что применимо к любому фону в CSS.
Наследование через currentColor делает иконки полностью «живыми» в контексте состояний:
.nav-link { color: #666; }
.nav-link:hover { color: #1a1a1a; }
.nav-link:active { color: #c0392b; }
/* Иконка меняет цвет вместе с текстом — без единой дополнительной строки */
Градиент задаётся так же просто, как обычный фон — и это единственный из рассмотренных методов, где градиент на иконке работает нативно, без хаков:
.icon-gradient {
background: linear-gradient(45deg, #f093fb, #f5576c);
}
Более того, в качестве заливки блока (т.е. цвета иконки) можно использовать растровые или векторные изображения, приметь фильтры, наложения заливок и т.д.
А CSS-переменные позволяют управлять цветом иконок глобально — например, при переключении темы:
:root { --icon-color: #3498db; }
.icon { background-color: var(--icon-color); }
[data-theme="dark"] { --icon-color: #74b9ff; }
Использование в псевдоэлементах
Это одна из важных сильных сторон метода — и, что принципиально, синтаксис здесь ровно тот же, что и для обычных иконок. Не нужно учить два разных подхода:
.menu-item::before {
content: "";
display: inline-block;
width: 1em;
height: 1em;
margin-right: 0.5em;
background-color: currentColor;
vertical-align: middle;
mask-image: url('arrow.svg');
mask-size: contain;
mask-repeat: no-repeat;
mask-position: center;
-webkit-mask-image: url('arrow.svg');
-webkit-mask-size: contain;
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: center;
}
Чтобы не повторять блок свойств маски для каждого псевдоэлемента, удобно вынести базовые стили в общее правило и передавать только URL через CSS-переменную:
/* Базовые стили — один раз */
[data-icon]::before {
content: "";
display: inline-block;
width: 1em;
height: 1em;
background-color: currentColor;
mask-image: var(--icon-url);
mask-size: contain;
mask-repeat: no-repeat;
mask-position: center;
-webkit-mask-image: var(--icon-url);
-webkit-mask-size: contain;
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: center;
vertical-align: middle;
}
/* Конкретная иконка — одна строка */
[data-icon="home"] { --icon-url: url('/icons/home.svg'); }
[data-icon="user"] { --icon-url: url('/icons/user.svg'); }
[data-icon="search"] { --icon-url: url('/icons/search.svg'); }
<a href="/" data-icon="home">Главная</a>
<a href="/profile" data-icon="user">Профиль</a>
Производительность
SVG-файлы загружаются как обычные внешние ресурсы и кэшируются по HTTP-заголовкам (Cache-Control, ETag). Если иконка используется на 50 страницах — файл загружается один раз. CSS-код не дублируется: общие свойства маски описаны один раз, URL файла — одна строка на каждый тип иконки. DOM остаётся чистым: один пустой <i> или <span> вместо целого SVG-дерева с десятками узлов.
Доступность
Иконка через mask-image визуально присутствует, но для скринридеров — это просто прямоугольник с фоном. Правило простое: если рядом есть текстовая подпись — скрыть иконку через aria-hidden="true". Если иконка единственный указатель на действие — описать родительский элемент через aria-label:
<!-- Иконка рядом с текстом — скрыть от скринридера -->
<button>
<i class="icon icon-save" aria-hidden="true"></i>
Сохранить
</button>
<!-- Иконка без текста — описать кнопку -->
<button aria-label="Закрыть">
<i class="icon icon-close" aria-hidden="true"></i>
</button>
Поддержка браузеров
Свойство mask-image поддерживается правктически во всех современных браузерах: Chrome 4+, Firefox 53+, Safari 4+ (с префиксом -webkit-), Edge 79+. На практике достаточно писать оба варианта — с префиксом и без — что и показано во всех примерах выше. Актуальное покрытие можно проверить на caniuse.com/css-masks.
Ограничения или когда есть смысл выбрать другой метод
Многоцветные иконки
Метод mask-image работает через альфа-канал SVG: он различает только прозрачные и непрозрачные области. Весь элемент залит одним фоном — цветом, градиентом или паттерном, но всегда чем-то одним. Это означает, что многоцветные иконки с независимо раскрашенными частями через этот метод сделать нельзя — здесь нужен inline SVG или спрайт с <use>, где каждый элемент получает свой fill через CSS-классы.
Анимированные иконки
Если нужно анимировать отдельные части иконки (например, вращение одной детали независимо от других) — тоже inline SVG: только он даёт прямой доступ к DOM-узлам внутри иконки.
Страницы с очень большим количеством иконок
Наконец, если на одной странице сотни уникальных иконок и критично минимизировать число HTTP-запросов — стоит рассмотреть SVG-спрайт в виде отдельного файла: один запрос на всю библиотеку иконок.
Итоговое сравнение
Добавим mask-image в таблицу и посмотрим на полную картину.
| Метод | Цвет через CSS | Градиент | Наследование |
Псевдоэлементы | Кэширование | Без дублирования | Многоцветность | Простота использования |
|---|---|---|---|---|---|---|---|---|
<img> |
✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ (внутри файла) |
★★★ |
background-image |
✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ (внутри файла) |
★★☆ |
| Inline SVG | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✓ | ★☆☆ |
SVG-спрайт + <use> |
~ | ~ | ✓ | ✗ | ✓ | ✓ | ✓ | ★★☆ |
| Иконочный шрифт | ✓ | ~ (хак) | ✓ | ✓ | ✓ | ✓ | ✗ | ★★☆ |
mask-image |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ★★★ |
mask-image — единственный метод, который одновременно закрывает все основные потребности: управление цветом, градиенты, наследование currentColor, работу в псевдоэлементах, кэширование и отсутствие дублирования. При этом он прост в использовании: новая иконка — это один SVG-файл и одна строка CSS, никакой пересборки, никакой дополнительной инфраструктуры. Единственное принципиальное ограничение — монохромность — для большинства интерфейсных иконок не является проблемой.
Заключение
mask-image — это метод, который объединяет то, что раньше требовало разных инструментов: управление цветом как у inline SVG, кэширование как у внешних файлов, работа в псевдоэлементах как у иконочных шрифтов — и при этом единый синтаксис для всех случаев. Добавление новой иконки не требует никаких дополнительных шагов: положил SVG в папку, добавил одну строку CSS — готово.
Там, где нужна многоцветность, по-прежнему остаётся inline SVG — и это нормально, потому что инструменты должны дополнять друг друга, а не конкурировать. Но для подавляющего большинства интерфейсных иконок — навигации, кнопок, форм, строк таблиц — mask-image закрывает задачу чище и проще любой из привычных альтернатив.