8 апреля 2012 в 00:55

Масштабируем CSS спрайты с SVG, убивая сразу трех зайцев

Привет, Хабр.
Сразу хочу отметить, что если мы говорим об иконках, их можно масштабировать двумя способами (других я просто не знаю): конвертировать иконки в шрифт и подключать их через @font-face, либо использовать SVG в качестве формата для этих иконок.

Немного отойду от темы и расскажу предысторию.

Предыстория


Я было решил использовать у себя на сайте шрифтовые иконки, казалось бы все хорошо: и размер менять можно, и цвет задавать и запрос к серверу всего один (на подключение шрифта). Другими словами, подключаемый шрифт это и есть своеобразный «CSS спрайт», верно?

Я давай проверять, везде ли все красиво выглядит. Оказалось, что не все так хорошо как хотелось бы, потому как в некоторых размерах иконки выглядели кособокими, а при отключенном сглаживании вообще противно смотреть на них стало. Что делать? Использовать второй вариант — SVG, о чем и пойдет речь.

CSS спрайт с SVG


Идея складывать спрайт в SVG формат не нова. Наверняка, многие читали этот пост на smashingmagazine. Так вот, я решил развить мысль автора, поэкспериментировать и предложить более гибкий вариант. А чтобы стало все понятно, некоторые примеры повторю здесь.

Итак, для начала нам нужно сделать SVG спрайт. Как сделать SVG файл программно я пока не разобрался (надеюсь сообщество меня простит и даст ссылку где про это почитать). Поэтому я буду делать свой спрайт в CorelDRAW (Illustrator думаю тоже подойдет) и сохранять в SVG.

Для быстрой реализации я использовал шрифт и напечатал вот такие иконки (кликабельно):

svg иконки

Возьмем произвольный HTML код для примера:
<div class="icons">
	<a class="tw" href="#">Twitter</a>
	<a class="fb" href="#">Facebook</a>
	<a class="vk" href="#">Вконтакте</a>
	<a class="gl" href="#">Google+</a>
	<a class="rs" href="#">RSS</a>
</div>

Напишем в CSS самое основное:
.icons a {
	float: left;
	display: inline-block;
	padding: 4px 0 4px 25px;
	margin-right: 5px;
	text-decoration: none;
	color: #444;
	/* вызов спрайта и задание размеров */
	background-image: url('sprite.svg');
	background-repeat: no-repeat;
	background-size: 20px auto;
}
/* такой вариант рассмотрен на smashingmagazine.com, только 
вместо px используются em как относительная величина */
.icons .tw {background-position: 0 0;}
.icons .fb {background-position: 0 -48px;}
.icons .vk {background-position: 0 -96px;}
.icons .gl {background-position: 0 -144px;}
.icons .rs {background-position: 0 -192px;}

Стоит отметить одну особенность, что sprite.svg создан с четко заданными размерами 76x520, т.е. максимальный размер до которого мы можем увеличить нашу иконку будет 76x76.

А что если сохранить SVG с относительными размерами, те. в процентах? Это ведь лучше, т.к. иконки теперь можно будет масштабировать до абсолютно любого размера и не терять при этом их качества.

Немного подправим стили в CSS:
/* этот блок остается без изменений */
.icons a {
	float: left;
	display: inline-block;
	padding: 4px 0 4px 25px;
	margin-right: 5px;
	text-decoration: none;
	color: #444;
	/* вызов спрайта и задание размеров */
	background-image: url('sprite.svg');
	background-repeat: no-repeat;
	background-size: 20px auto;
}
/* а здесь меняем абсолютную величину на относительную */
.icons .tw {background-position: 0 0;}
.icons .fb {background-position: 0 -25%;}
.icons .vk {background-position: 0 -50%;}
.icons .gl {background-position: 0 -75%;}
.icons .rs {background-position: 0 -100%;}

Результат одинаковый, но background-size может быть любым, каким угодно.

Создание сложных спрайтов


В примере выше использовано всего 5 иконок и высчитать проценты не так сложно. Давайте рассмотрим более сложный пример. Пока с теми же «шрифтовыми» иконками. Допустим у нас такой спрайт:



Что мы делаем, либо добавлем в html новый тег i, для которого можем написать стили с иконкой:
<div class="icons">
	<a class="tw" href="#"><i></i>Twitter</a>
	<a class="fb" href="#"><i></i>Facebook</a>
	<a class="vk" href="#"><i></i>Вконтакте</a>
	<a class="gl" href="#"><i></i>Google+</a>
	<a class="rs" href="#"><i></i>RSS</a>
</div>

Либо оформляем иконку используя псевдоэлементы ::before или ::after. Я буду использовать псевдоэлемент ::before, вы — как вам больше нравится.

Обратите внимание, что я не стал придумывать новые классы для ссылок (ведь спрайт поменялся) и оставил прежние, разумеется для новых иконок классы будут свои. А мы рассмотрим с этими, чтоб не захламить кодом пост.

Итак, внесем изменения в CSS:
.icons a {
	float: left;
	display: inline-block;
	padding: 4px 0 4px 25px;
	margin-right: 5px;
	text-decoration: none;
	color: #444;
	/* добавляем */
	position: relative;
}
/* создаем общий стиль для ::before */
.icons a::before {
	position: absolute;
	left: 0;
	top: 0;
	content: '';
	width: 25px;
	height: 25px;
	/* вызов спрайта и задание размеров */
	background-image: url('sprite.svg');
	background-repeat: no-repeat;
	background-size: 20px auto;
}
/* а здесь дописываем псевдоэлемент и задаем позицию в спрайте
(соответственно для нового спрайта позиции будут другие, о чем ниже) */
.icons .tw::before {background-position: 0 0;}
.icons .fb::before {background-position: 0 -25%;}
.icons .vk::before {background-position: 0 -50%;}
.icons .gl::before {background-position: 0 -75%;}
.icons .rs::before {background-position: 0 -100%;}

Сделаю несколько пояснений, а то я уже сам чуть было не запутался :).

В background-size: 20px auto; число «20» это необходимый нам размер иконки, а «auto» это оставшийся размер спрайта. Если мы заменим «auto» например на 20px, то у нас вместо одной иконки получится весь спрайт размером 20x20 пикселей.

Кроме того, на размер иконки теперь еще влияет ширина и высота блока формируемая псевдоэлементом, т.е. вместе с background-size теперь нужно менять width и height, чтобы иконка не обрезалась.

Высчитываем относительные размеры

Пожалуй, это ключевой момент и здесь стоит сделать выбор, либо вы задаете абсолютные размеры и при изменении background-size, width и height меняете их тоже, либо (что сложнее) высчитываете относительные размеры и при изменении background-size, width и height больше ничего не меняете.

Итак, фактический размер спрайта 500x250 пикселей, по 10 иконок в строчку и по 5 в столбик, итого 50 иконок (в примере 49) размером 50x50 пикселей каждая.

Высчитать размер довольно просто, т.к. будем отталкиваться от фактического размера спрайта. Кто работал со спрайтами объяснять не стоит. Правда здесь есть одна тонкость — размер иконки у нас уменьшен до 20 пикселей, соотвественно спрайт тоже изменился и стал равен 200x100 пикселей (10*20 и 5*20), а значит и размеры (background-position) мы будем считать, либо 0 0, 0 -20px, -20px 0, -20px -20px, 0 -40px, -40px 0, -40px -40px и т.д., либо 0 0, 0 -11,1%, 0 -22,2%, 0 -33,3, 25% 0, 25% -11,1%, 25% -22,2%, 25% -33,3 и т.д.

Таким образом мы можем сделать любой сложности спрайт и высчитать background-position для каждого элемента. К счастью или сожалению, пытливый ум не дает остановиться на сказанном, поэтому бегло рассмотрим еще более сложный пример.

Более сложный спрайт с SVG


Допустим, у нас есть некий дизайн, совсем приблизительно изобразил (кликабельно):

any site

Предположим, что прямоугольники это какие-то красивые клипарты, трудно, но предположим. Что мы можем сделать:
1. Сложить все слои в спрайт не меняя исходный размер и позиционировать каждый объект в соответствующем элементе;
2. Сложить все слои в спрайт, предварительно приведя все объекты к одному размеру и позиционировать каждый объект в соответствующем элементе меняя размер объекта до неоходимой величины.

Приводить пример HTML или CSS кода, думаю, уже не имеет смысла. И так все понятно, настраивается по аналогии с предыдущими примерами.

В заключении


Теперь хочу подвести итог и отметить плюсы спрайтов с SVG. Во-первых, у нас получился всего один файл, а значит один запрос к северу — заяц1 убит, во-вторых, вес SVG файла гораздо меньше, чем например PNG или JPG, а значит и скорость загрузки выше — заяц2 убит, в-третьих, мы получили неограниченных размеров спрайт без потери качества изображения, а значит решили задачу с неограниченным масштабированием изображения — заяц3 убит.

Единственный минус SVG перед шрифтовыми иконками: нельзя украшать иконки с помощью CSS, например, добавлять text-shadow или менять цвет. И очень большой плюс, что при отключенном сглаживании все линии в SVG будут ровные и четкие в отличае от шрифта.

P.S. SVG дает нам огромное поле для деятельности и на примере CSS спрайтов я в этом полностью убедился. Конечно, можно было обойтись сухими словами и сказать «сохраняйте SVG в относительных размерах», но у меня это вылилось в целый пост.

Спасибо за внимание. До новых встреч.

UPD: ::before и ::after это псевдоэлементы, а не псевдоклассы — простите, ошибся, поправил. Спасибо psywalker вовремя подсказал.
UPD: А про "::" я и не вспомнил :( Пруф
Konstantin Demidovich @Sytrus
карма
23,0
рейтинг 0,0
Похожие публикации
Самое читаемое Разработка

Комментарии (73)

  • +3
    Рендеринг SVG явно медленнее, чем рендеринг растровой картинки. Плюс неизвестно, может браузер ради каждой иконки будет рендерить SVG-файл целиком. Непонятно, как это скажется на производительсности и потреблении памяти. Непонятно, что делать с IE.

    То есть минусов много, плюс — всего один, и то в большинстве случаев ненужный.
    • НЛО прилетело и опубликовало эту надпись здесь
      • +7
        Типичное мнение того, кто только верстает и не имеет свой сайт/стартап/магазин/да даже бложик и не запаривается за целевую аудиторию.
        • НЛО прилетело и опубликовало эту надпись здесь
          • +2
            Заносчивость? А класть на ИЕ 6-8 (примерно 25% рунета) — это не заносчивость?
            Кстати, у меня нет ниодного своего сайта и гуру я себя несчитаю, но мозгов хватает, чтоб осознавать, что поддерживать ИЕ надо.
            • НЛО прилетело и опубликовало эту надпись здесь
              • +2
                Можно изначально не выделываться за новейшие технологи и делать стандартными рабочими средствами без необходимости делать одну работу много раз. И все так и делают. И правильно делают.
                • НЛО прилетело и опубликовало эту надпись здесь
                  • 0
                    Нам надо с веткой для беседы определиться)

                    > Это вы ради красного словца?

                    Нет, я конкретно про svg-иконки имел в виду. ПНГ сто лет, как не проблема, если юзать библиотеку pie (-pie-png-fix и -pie-background), кроме того, пнг можно и не фиксить в 6. В данном случае это допустимо, я считаю. Т.к. пользователь увидит данные, пусть слегка коряво, но увидит.

                    > Тонны костылей

                    Да какие тонны, ей богу. Меня ИЕ 7-8 ну вообще никак не напрягают (5-6 хаков inline-block — это минута времени). Шестой, да, может отнять лишний час, но это некритично и не двойная работа, а просто шилфовка определенных мест под него.

                    > Или может не в IE свое отдельное виденье DOM?
                    А какие проблемы у ИЕ с DOM?
                  • +2
                    И еще, к слову. ИЕ — вообще няшка. Когда в ИЕ6 был уже VML (считай почти svg), матрица преобразовний (это считай почти полноценный css transform), ajax (еще в ИЕ5), всякие фильтры градиентов, теней и еще сотни приколюх — другие браузеры тихо курили в сторонке и молчали. И это их приходилось поддерживать. Их стало больше, w3c стало рулить, взяв многое из того, что придумалы майкрософт и почему-то вы «правильм» считаете теперь именно их стандарты, а ИЕ костылем, который поддерживать — лишнее, а не наоборот. Неправильно это, я считаю.
                    • НЛО прилетело и опубликовало эту надпись здесь
                      • НЛО прилетело и опубликовало эту надпись здесь
                        • +1
                          Это не просто нормально, а просто отличная возможность. Например, без джаваскрипта вы вообще не сможете отделить стили фф 10 от фф 4. Тоже с оперой и хромом. Хоть и редко, но порой бывает нужно, т.к. не все браузеры и их версии одинаково поддерживают стандарты.

                          Иными словами, возможность отделять версии ИЕ — это благо)

                          А если вы вообще о том, что на сатайх эти комментарии с ксс-факлом для каждой версии — это уже другой разговор. Лично я предпочитаю хаки, не вижу ничего страшного в невалидном ксс-файле. А если говорить о двойной работе, то вот например ксс файла ИЕ7 вконтакта (сайта с достаточно сложной версткой и набором js-фич). Не так уж и много кода написано для ИЕ7. Всего лишь 15 селекторов. Что заняло, возможно, минут 5-15 лишнего времени, не катастрофа.
            • 0
              75 % Рунета должно быть более чем достаточно кому угодно, кроме, разве что, Центризбиркома.
      • +1
        Под IE сидит достаточно большой пласт людей. Выбрасывать их как аудиторию, либо переучивать точно не вариант, поэтому нужно просто оптимизировать разработку под них и собирать денюжки
        • НЛО прилетело и опубликовало эту надпись здесь
          • 0
            По-вашему Сергей Чикуёнок школота? Посмотрите это видео, возможно вы измените свои взгляды. Хотя мне все равно, делайте как хотите, можете хоть вообще только под Chrome верстать и не париться, у вас же «нишевые разработки».
            • НЛО прилетело и опубликовало эту надпись здесь
              • 0
                И уж конечно я имею право высказать свое мнение об этом чуде инженерной мысли, об IE, хотя и рискую словить тонны ненависти от хабрашколоты

                В видео как раз говорится о поддержке IE. Если у вас нет пользователей с IE, не поддерживайте, но не нужно «совать два пальца в рот» каждый раз когда речь идет об IE, надоело ей богу.
                • НЛО прилетело и опубликовало эту надпись здесь
                  • +2
                    Это вопрос восприятия. Я не считаю, что учет ИЕ при верстке — это лишняя работа. Что лишнего в том, что я потрачу пусть даже пару часов (а обычно не более 15 лишних минут) времени, пусть даже прикручу какой то мегакостыль, чтоб 25% юзеров увидели номр картинку? Лишняя работа — это когда она действительно ненужная и бессмысленная и не несущая результат.
              • +2
                Вы сами искажаете факты. Сто лет никто не делает верстку отдельно под ИЕ и норм браузеры (примерно с появления w3c). У меня одна верстка под ИЕ6 и хром. Да, написано пара хаков для inline-block и png в ie6, в остальном нет никаких проблем, если делать правильно. Конечно, если вас комар в лоб укусит и вы решити запилить себе svg иконки (ток зачем?), то придется потратить немного времени, а может и совсем чуть-чуть, если использовать Raphaël js.
                • НЛО прилетело и опубликовало эту надпись здесь
          • 0
            Да, и еще пользователь сайта должен иметь доступ в интернет, кэп
    • –1
      На счет производительности не скажу, не нашел материала по этому вопросу. Поддержка только IE9+, по сути, ровно та же что и @font-face.
      • +2
        неправда, ие первым вообще начал поддерживать font-face ;) посмотрите
        • 0
          Действительно.
        • 0
          В любом случае мой выбор в пользу SVG. А для IE на крайний случай можно и PNG картинку грузить.
    • +1
      Есть возможности более продвинутого использования svg без недостатков которые вы перечислили. Я говорю о data:url. Есть два вида использования этой формы подключения одна стандартная типа «background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4», а есть вторая которая дает просто умопомрачительные возможности в связке с less/sass что то типа такого cssdesk.com/WpYTE.

      В данной интерпретации есть возможность в css менять не только размер изображения, но и цвета используемые в иконке в зависимости от цветовой схемы. Единственный минус второго способа это пока работает только на вебките. Второй работает во всех браузерах кроме ие, да и то в ие10 тоже будет работать. Для ие старых версий можно сделать вторую версию изображений в растре что бы в последствии отключить когда настанет светлое будущее:)

      Могу сказать что все чаще появляются заказы на разработку полной версии для новых браузеров и с минимальной поддержкой ослоподобных. Так что разговоры о том что если это не работает в ие6-7 то это не жизнеспособно уже прошлое. Веб меняется в лучшую сторон и поэтому не стоит ждать светлого будущего, а всеми силами приближать его объясняя заказчикам все плюсы плавной деградации верстки и использования новых технологий.
      • 0
        Хороший пример, я бы даже сказал, универсальный. А как получить такой SVG файл?
        • +1
          Экспорт из иллюстратора с некоторыми правками: нужно убрать лишнее, убрать все пробелы и заменить кавычки что бы правильно парсилось.
          • 0
            Отлично!
      • 0
        Хотя стоп. А как быть, если у нас спрайт где много картинок, а не одна?
        • +2
          Зачем вам спрайт где много картинок при использовании data:url?

          Сшивание картинок в одну используется для уменьшения количества запросов к серверу. При подключения изображений прямо в css идет один запрос к серверу и не нужно делать «сшивку» изображений. Профит...:)
  • +3
    Думаю, статья в любом случае будет полезна для новичков и людей, не работавших с svg. Поэтому, спасибо за статью, очень интересно!
  • +1
    Более красивый подход SVG спрайтов (SVG Stacks) был предложен по ссылке. Из проверенных мною браузеров заработало в ie9 и Firefox. В Chrome отказалось работать напрочь, а Opera 11.62 показала кривой результат.

    + вам стоило бы употреблять верное написание "::before" и "::after" наряду с ":before" и ":after" в академических целях, дабы лить свет в массы ;).
    • +1
      + посредством якорей можно менять стили в самих спрайтах. К примеру, "...#fb" — обычная иконка facebook'а, а "...#fbRed" — она же, но красного цвета. Когда в селекторах появятся регулярки, тогда все это поднимется на новый уровень :).
      • 0
        Поясните, при чем тут якоря? Я не понял :)
        • 0
          за счет css-правил внутри svg-файла показывается только тот спрайт, который указали с помощью якоря в урле на svg-файл
          • 0
            Я уже понял :) и ответил здесь
    • –1
      Вы имели в виду, что «красивый подход» это использование iframe? Хм… а какие по вашему плюсы в таком подходе?
      + вам стоило бы употреблять верное написание "::before" и "::after" наряду с ":before" и ":after" в академических целях, дабы лить свет в массы

      Я даже префиксы не стал писать для background-size, а вы говорите про «двойные» псевдоэлементы. Зачем захломлять код? Решение не этом.
      • +1
        Отвечу сразу на оба комментария. Вы по ссылке ходили? Там все описано и даже пример на jsfiddle дан. Ну а "::before" и "::after" исключительно для общего развития, ибо все по старинке так и пишут как в CSS2.
        • 0
          Вы по ссылке ходили? Там все описано и даже пример на jsfiddle дан.

          Ага, понял. Но здесь SVG делается «программно», о чем я писал, что еще не разобрался с этим. Может у вас и ссылка есть где про это подробно написано? Здесь описания не увидел.
          • +1
            Хм, ну описание устройства SVG можно почитать где-нибудь в спецификации, аль MDN (MDC). Как я понимаю, то под SVG Stacks попадает любой статичный SVG-файл, а может быть даже и динамичный (с js).

            Допустим возьмем знаменитого тигра. Переделанный пример:

            fiddle.jshell.net/LZ6a5/3/show/#tiger
            fiddle.jshell.net/LZ6a5/3/show/#chart
            fiddle.jshell.net/LZ6a5/3/show/#plus

            Один файл и много изображений (да, выглядит криво, ибо не знаю, как в jsfiddle чистый svg вставить без обертки на html, но цель была показать, что это работает). Ну а исходник можно в примере посмотреть.
            • 0
              Спасибо за MDN, надо будет почитать да разобраться, а то даже возразить нечем :)
      • +1
        Этот хлом поможет тем, кто не знает как надо писать. Сейчас же они подумают что так и надо.
        • 0
          Поправил, спасибо.
        • 0
          А я думал что одно двоеточие вместо двух ставят в целях более широкой поддержки браузерами.
          • 0
            Чтобы псевдоэлементы хоть как-то отличались от псевдоклассов придумали это двойное двоеточие.
            • 0
              Я знаю для чего их придумали, я писал почему всё-таки пишут одно.
              • 0
                Думаю, по-привычке из CSS 2.1. Новые версии браузеров понимают оба написания, а вот про старые не скажу, не в курсе, особо не вникал в это. Хотя интерено, что если в стилях стоит "::", FireFug все равно показывает ":"…
                • 0
                  А хром, если мне не изменяет память, ведёт себя наоборот — :after отображает как ::after. И тем не менее тащим IE8.
  • 0
    А разве нельзя для масштабирования растрового-спрайта использовать background-size с фолбеком для IE8- в виде zoom с нужным масштабом?
    • 0
      А качество изображения?
      • 0
        А что с ним не так?
        • 0
          автор считает, что основная проблема в сайте, который увеличили на 1000% — это то, что в нем будут размазанные иконки.
          • 0
            Дело далеко не в увеличении страниц сайта, а в масштабировании используемой графики.
        • 0
          Отвечу скриншотом. Догадаетесь где скрин с PNG, а где с SVG:
          • –1
            Что мешает нарисовать иконки не 32х32, а 128х128 и просто уменьшать их по мере надобности?
            • +3
              * фоновые картинки сложно масштабировать
              * браузеры не очень красиво масштабируют графику
              * масштабировать графику на стороне клиента — очень дорого (читайте как ресурсоемко)
            • +2
              Например, растровая иконка 128x128 будет весить больше, чем 32x32. А в SVG — размер файла будет почти тот же.
  • –1
    По поводу третьего зайца.
    Из той статьи, что в smashingmagazine:
    sprite.png: 1064 байт
    sprite.svg: 2411 байт
    Конечно, надо помнить про gzip:
    sprite.svg.gz: 1115 байт, все равно больше чем sprite.png, но стоп, мы же не пробовали сжать png?
    sprite_new.png 530 байт
    Итого, разница в 4 раза, если забыли включить на сервере gzip и 2 раза если его таки не забыли включить.
    • +2
      1. Я не считал размеры спрайта на smashingmagazine, мой с соц. иконками получился 4431 байт SVG и 69 772 байта PNG — ну сожмем мы еще до 17 000 байт например, это все равно будет больше, чем получивышися в SVG.
      2. SVG мы сможет увеличить до неограниченно любого размера, а вот PNG нет.
      • 0
        Не знаю как вы считали, но у меня fb635d591a788d1107fd0c3e73b357e1.png -> 10067 байт.
        После сжатия получил файл размером 3890 байт, и кажется, это меньше, чем вам svg.
        • 0
          А тот файл, где много разных иконок получился весом в 8389 байт, сколько весит его svg брат?
        • 0
          Выложил оригинальный размер SVG файла www.divmotive.ru/_frm/habr/icons.svg Хотите, конвертируйте в PNG и смотрите какой будет вес… Файл SVG пересохранил заново, его размер чуть отличается от указанного мной выше. Говорю об этом, потому что чувствую вы и к этому можете придраться :)
      • 0
        Зачем вам неограниченной любой размер?
        Приведите 10 примеров из жизни.
        • 0
          10 примеров это конечно круто, а вот 2 приведу:
          1. можно в один спрайт сложить изображение как иконки, так и клипарта (последний пример видимо было раскрыть полностью);
          2. иконка сегодня нужна 20x20 пикселей, а завтра мне захотелось ее увеличить или уменьшить — рисовать новые? да для адаптивной разметки это очень хорошо: меняй размеры в зависимости от окна браузера как хочешь. Разве не круто?

          А если посидеть подумать, наверняка, можно еще пару пунктов добавить.
  • 0
    Подскажите, с какого шрифта вы иконки брали?
    • +2
      • 0
        Там же вроде и SVG спрайт можно сделать. Или я ошибаюсь?
        • 0
          Получается шрифт в SVG, который можно конвертировать в .ttf или другой шрифтовой формат и использовать как шрифт! А для спрайта нужны графические объекты.
  • НЛО прилетело и опубликовало эту надпись здесь

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.