Когда мы в компании экспериментировали с inline SVG пиктограммами , то обнаружили разные браузеры реагировали различным образом на то, как мы ссылались на SVG с набором иконок. Нам хотелось получить универсальный и стабильный метод, который бы работал одинаково предсказуемо во всех браузерах, поддерживающих SVG.
Цепочка проб и ошибок привела нас к такой схеме:
- Преобразуем SVG-файл в строку текста и присваиваем её какой-либо переменной.
- Объединяем JavaScript файл, который мы получили на предыдущем этапе, с другими JS-файлами, которые подключаются в
<head>
. - Вдо первого использования SVG добавляем пустой элемент — плейсхолдер и вставляем в него содержимое SVG-строки.
Таким образом, мы добились кеширования пиктограмм, как и любых других статических файлов, а так же обошли проблему подключения SVG из внешнего файла в IE.
<svg xmlns="http://www.w3.org/2000/svg" class="icon_circle">
<use xlink:href="#circle"></use>
</svg>
Опишу все проделанные шаги подробнее.
Конвертирование SVG в JS
Этой задачей занимается Grunt.
grunt.registerTask('elements', 'Transform a SVG sprites to a JS file',
function () {
var LINE_LENGTH = 100, svg = [], i, l, content;
content = grunt.file.read('assets/images/elements.svg');
content = content.replace(/'/g, "\\'");
content = content.replace(/>\s+</g, "><").trim();
l = Math.ceil(content.length / LINE_LENGTH);
for (i = 0; i < l; i++) {
svg.push("'" + content.substr(i * LINE_LENGTH, LINE_LENGTH) + "'");
}
grunt.file.write('assets/_/js/elements.js',
'var SVG_SPRITE = ' + svg.join('+\n') + ';');
}
);
Из файла assets/images/elements.svg
получается файл assets/_/js/elements.js
.
Конкатенация
Файл elements.js
должен быть загружен в <head>
Наверняка у вас есть ещё несколько скриптов, которые тоже требуется загружать в начале страницы. Их можно объединить между собой чтобы уменьшить количество HTTP-запросов к серверу.
Добавление на страницу
Важно, чтобы спрайты появились на странице до их первого использования. Добавим на страницу пустой элемент.
<div id="elements-placeholder"
style="border: 0; clip: rect(0 0 0 0); overflow: hidden;
margin: -1px; padding: 0; position: absolute;
width: 1px; height: 1px;"></div>
Плейсхолдер нужен для того, чтобы безопасно модифицировать DOM-дерево в процессе загрузки страницы. У него должен быть уникальный id
. Я ещё добавил некоторые inline-стили, чтобы защитить страницу от эффекта «упячки», если контуры случайно окажутся не помещены в <defs>
.
Заполняем плейсхолдер.
<script>document.getElementById("elements-placeholder").innerHTML = SVG_SPRITE;</script>
Замечания
-
При формировании JS-файла для простоты полагается, что будет создана глобальная переменная
SVG_SPRITE
. Чтобы не загрязнять глобальную область, я рекомендую сохранить содержимое SVG в пространство имён приложения. - Имена файлов жёстко прописаны в Grunt-задаче. Есть смысл задачу сделать конфигурируемой, если у вас в проекте несколько наборов SVG-пиктограмм.
Демонстрация
У нас в компании есть проект-шаблон , в котором можно детально посмотреть эту технику подключения SVG.
Коментарии к заметке
А возможно ли для таких svg-спрайтов сделать деградацию в png?
Да, конечно можно сделать деградацию. Допустим, на странице у тебя пиктограмма используется так:
В CSS, соответственно, для
.icon-close
указываем нужные стили (размер и цвет):С помощью Modernizr проверяем возможность отображения SVG. Если её нет, то подставляем фоном обычную картинку.
А как побороть неокругление размеров до целых пикселей в IE11? Из-за него svg-иконки становятся нерезкими.
Пример
Из-за дробного
line-height
и размеров в em векторные иконки не попадают в пиксели по вертикали, когда находятся в потоке. Но если иконкам прописатьposition: absolute; top: 200px;
(целое число пикселей), то иконки становятся резкими, как и должны быть.Ох. Я понял твою проблему, Павел. К сожалению, у меня решения нет. Я бы попробовал включить какие-нибудь свойства, создающие отдельный слой, (
transform
,backface-visibility
и т.п.) в надежде, что они округлят позиционирование блока.С
transform
и т.п. не прокатило. В результате по-умолчанию решил использовать традиционные спрайты, сделанные черезbackground-image: url(sprite.svg)
, а inline SVG применять только для крупных иконок, где размазывание уже не критично.Спасибо за советы!
Насколько я понял, у вас всегда грузится весь набор иконок, даже если половина из них не используется на странице. Вы как-то решаете этот вопрос?
Андрей, да, загружается всегда весь набор. С этим нет особых проблем, так как объём 50-70КБ (в сжатом состоянии и того меньше). При правильной настройке HTTP-заголовков файл берётся из кеша браузера при переходе по страницам сайта. Проведено множество исследований, доказывающих, что лучше загрузить как можно больше (в разумных пределах, разумеется) скриптов разом, чем загружать отдельные файлы. Интернет у большинства посетителей достаточно быстрый. Но очень много времени тратится на подключение к серверу.
В чем преимущество данного метода по сравнению с:
Почему JS необходимо подключать именно в header? Возможно ли подключить его внизу body каким-либо образом?
Спасибо
Как насчет заюзать аякс и уйти из хеад?
<use>
ты можешь делать трансформацию контуров, перекрашивание и т.п.Не понял твою мысль. Как браузер может закешировать часть страницы?
Потому что « важно, чтобы спрайты появились на странице до их первого использования ». В некоторых старых на сегодняшний день браузерах мы замечали, что иконки не появляются, если при первой отрисовке их не было.
Загрузка через ajax тоже будет работать аналогично с дефектами. Сначала появится использование контура, а потом когда-нибудь браузер загрузит сами контуры.
Возможно, доля браузеров с такой ошибкой сейчас уже пренебрежимо мала. Но два года назад для нас это было актуально. Я честно не знаю как обстоят дела с этим сейчас.
Есть совершенно другой вариант, который соответствует сегодняшним тенденциям.
Итак, скрипты переносят в самый конец страницы, чтобы их загрузка не влияла на отрисовку основного контента. Так как иконки — это вполне себе второстепенный контент, то можно отложить их загрузку и отрисовку на потом. На странице ты оставляешь пустые заглушки и загружаешь скрипты с контурами и инициализацией после контента (возможно даже с использованием атрибута
async
).Пример:
После загрузки скрипта с контурами, ты заменяешь все
icon-placeholder
на реальные<svg>
:Только не забудь правильно расставить классы и стили, чтобы заглушки имели те же самые размеры. Тогда браузера отработает только фазу «repaint». Если размеры у заглушки и пиктограммы не будут указаны, то браузер будет делать ещё и «reflow», что может быть очень заметно для пользователей.
В итоге ты получишь:
<head>
;Сделал плагин gulp-svg2string для конвертации в строку.