Пользователь
0,0
рейтинг
28 января 2013 в 13:19

Дизайн → Cила CSS поможет тебе принять форму, SVG! tutorial


Нам нужно:
  1. Установить на сайте собственные иконки с помощью SVG.
  2. Они должны управляться с помощью CSS (форма, размер, заливка, эффекты в том числе и их поведение).
  3. Они должны иметь маленький вес и находиться в одном месте для экономии http запросов.
  4. Работать во всех основных современных браузерах.

демо

Зачем я это пишу?


Несмотря на растущую популярность вектора в браузере, его возраст и поддержку браузерами, хороших решений использования, как кажется мне, очень мало. Позвольте объясниться. Конечно о SVG много писали и рассказывали. Даже о SVG и CSS вместе. Но когда я столкнулся с необходимостью сделать SVG иконки для сайта, не смог найти хороших гибких решений. SVG в браузере сейчас выглядит всеми забытым пожилым человеком, пора его причесать, встряхнуть от пыли и отправить в спорт зал. Надеюсь описанный в этой статье метод для кого нибудь будет полезным.

Сразу скажу да, я использовал иконочные шрифты, вот в чем здесь проблемы:
1. Такие иконки в браузере рендерятся как шрифт и в Windows, например, часто получаются мыльные края. Есть CSS свойства, которые должны решать эту проблему, но они работают только в WebKit и только под MAC — то есть бесполезны. Дизайнер ругался.
2. Только в 23 Chrome и только под Windows такой шрифт начал исчезать, а в некоторых случаях сильно «рвать» остальную верстку сайта. Я много раз пользовался такими шрифтами, но первый раз со своими собственными иконками. И первый раз такая проблема.
3. Невозможно добавлять внутреннюю тень. В проекте это было обязательно. Дизайнер ругался.
4. Вес такого шрифта. C SVG не сравнится.
5. Все-таки SVG имеет больше возможностей по сравнению со шрифтом.

Приступим или исходные данные.


Исходные данные — чиcтый HTML документ с подключенным к нему main.css или то место, где мы будем писать стили наших иконок.
Так же добавим SVG документ в тег body. В нем, в разделе defs, мы будем декларировать формы наших иконок в виде clip-path и фильтры для них (внутренняя тень).
Как декларировать SVG лучше подсмотреть у вредного старичка, так как он самый превередливый.

<!doctype html>
<html>
  <head>
    <meta charset=utf-8 />
    <title></title>
    <link rel="stylesheet" href="main.css">
  </head>

  <body>

    <?xml version="1.0" standalone="no"?>
    <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
    "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
      <svg version="1.1" xmlns="http://www.w3.org/2000/svg">
         <defs>

           <!-- здесь будут декларироваться формы наших иконок и внутренние тени к ним -->

         </defs>
     </svg>

  </body>
</html>


Добавляем форму иконки


Как получить SVG код, я думаю, писать не нужно? Достаточно просто открыть SVG файл в текстовом редакторе или воспользоваться любым векторным редактором, например Illustrator. Для наглядности здесь я показываю только SVG код из нашего HTML документа (помните, что он находится в теге body). Создаем тег clipPath и добавляем в него SVG форму иконки.

<?xml version="1.0" standalone="no"?>
 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
    "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 <svg version="1.1" xmlns="http://www.w3.org/2000/svg">
    <defs>


     <clipPath id="heart-path">

          <path fill-rule="evenodd" clip-rule="evenodd" d="M256,512c0,0-256-144.938-256-311.694C0,29.22,240.62,10.145,256,192 c18.467-181.721,256-162.784,256,8.306C512,367.062,256,512,256,512z"/>

     </clipPath>


     </defs>

 </svg>


Мы создали путь для иконки в виде сердца. Прошу обратить внимание на аттрибут id="heart-path" у тега clipPath.
Через него мы будем ссылаться на форму нашей иконки.

Добавляем иконки


Вставляем в HTML документ нашу иконку. Она состоит из маленького SVG документа, внутрь которого вставлен квадрат:

 <svg class="heart-icon icon" viewBox="0 0 512 512">
      <rect width="512" height="512" />
 </svg>

Сразу видим небольшой недостаток подхода — иконка состоит из двух элементов. Проблема, на мой взгляд, терпимая так как структура у всех иконок абсолютно одинаковая. Если что, можно легко найти/заменить/убрать через поиск по всему проекту.

Обратите внимание на цифру 512. Это размер квадрата в который была вписанна иконка, когда рисовалась в редакторе. С Вашей иконкой может быть иначе. Класс heart-icon для задания формы иконки в CSS, класс icon — объединяющий класс для всех иконок (их же будет масса?).

Последний шаг. Добавляем CSS


Давайте посмотрим что получилось. Если Вы все сделали правильно, то увидите черный квадрат 512 на 512 пикселя. Малевич уже одобряет. Но, боюсь, наш с вами манифест супрематизма никто уже не оценит. Продолжаем.

Все правильно, иконок нет? Приступаем к самому интересному — CSS.

В main.css который мы подключили к документу пишем стили для классов heart-icon и icon.

.icon{
  width:32px;
  height:32px;
  cursor:pointer;
  fill: #ccc;
}
Наш черный квадрат становится серым и 32х32 пикселя. Свойство fill задает заливку нашей иконки.

.heart-icon rect{
  clip-path:url('#heart-path');
}
Наконец-то появляется форма иконки! clip-path именно то cвойcтво, которое заставляет браузер брать форму по #heart-path и применять ее к квадрату.

Добавим поведение для иконки. Это будут :hover, :active состояния и checked класс.

.heart-icon rect{
  clip-path:url('#heart-path');
}

.icon:hover{
  fill: #999;
}

.icon:active, .icon.checked{
  fill:red;
}



Для наглядности я скопировал иконку и добавил ей класс checked.
Вот и все, наша система готова. Да, она упрощенна для этого примера. В моем проекте, я бы задавал цвет для разных иконок и соответственно поведения через отдельный класс. Ведь иконки у нас могут быть разных цветов и поведение может быть разным (не все же иконки серые). Но не забивайте этим голову, давайте сейчас держать вещи простыми.

Чтобы добавить новую иконку в нашу систему, нужно всего лишь:

  1. Задекларировать новый clip-path c формой иконки и своим id в общем SVG документе (в разделе <defs>).
  2. Создать новый класс для формы иконки в CSS.

Так просто.

Я добавил иконку плей так как код ее очень простой и хорошо иллюстрирует, что в качестве clip-path можно использовать не только пути, но и любые фигуры (которые поддерживает SVG конечно).

Добавляем внутренюю тень


Для этого мы будем использовать SVG filter.
Сразу предупреждаю. Для данного примера у меня возникали небольшие трудности с иконками при использовании фильтра поверх них. Часть из них иногда исчезала. Иногда. Может только у меня. Будьте на чеку. И еще одна проблема — чтобы фильтр накладывался красиво нужно добавить еще один элемент в иконку. Да, очень жаль. Теперь иконка с фильтром = 3 элемента. Так что если Вам нужно воплатить трендовый флет дизайн, смело перематывайте.

Добавим элемент:
 <svg class="heart-icon icon" viewBox="0 0 512 512">
    <g>
      <rect width="512" height="512" />
    </g>
 </svg>
Тэг <g> и есть тот самый элемент, который используется для фильтра.

Добавляем в раздел SVG документа фильтр:
<filter id='inset-shadow'>
    
    <!-- Сдвиг тени -->
    <feOffset
      dx='0'
      dy='0'
    />
    
    <!-- Размытие тени -->
    <feGaussianBlur
      stdDeviation='20'
      result='offset-blur'

    />
    
    <!-- Инвертируем drop shadow
         чтобы создать внутреннюю тень -->
    <feComposite
      operator='out'
      in='SourceGraphic'
      in2='offset-blur'
      result='inverse'
    />
    
    <!-- Цвет и Прозрачность -->
    <feFlood
      flood-color='black'
      flood-opacity='.65'
      result='color'
    />
    
    <!-- Обрезаем цвет внутрь тени -->
    <feComposite
      operator='in'
      in='color'
      in2='inverse'
      result='shadow'
    />
    
    <!-- Раcполагаем тень поверх элемента -->
    <feComposite
      operator='over'
      in='shadow'
      in2='SourceGraphic'
    />
  </filter>


Описывать как работает фильтр, я не стану. Вы запросто прочитаете это в интернете. Это ведь тема для отдельного поста. Просто покрою его комментариями.
Обратите внимание что у фильтра тоже есть id='inset-shadow'

Добавляем к иконкам тень
.icon g{
  filter:url('#inset-shadow')
}

Вот и все. Хочется мне написать. Но это было бы неправдой.

Правда или любим всех


Дело в том, что если вы откроете пример в Opera, то увидите набор из 3 квадратов. Те иконки, что с тенью и вовсе исчезают и это проблема SVG фильтров. Если браузер не находит нужный фильтр, он рендерит иконку прозрачной, вместо того, чтобы просто его не применять.

Но почему же не находит?! Дело в том, что здесь сталкиваются 2 браузерных мира. Наш путь url('#heart-path') Opera воспринимает как путь относительный файла CSS или url('main.css#heart-path') вместо url('index.html#heart-path'), как делают это остальные. Если задать путь эксклюзивно как url('index.html#heart-path'), то браузеры не парсят SVG документ внутри index.html, так как считают его внешним источником. Такие же проблемы возникают и у Mozilla, как только Вы переносите main.css за пределы каталога с файлом index.html. IE же в этом вопросе солидарен с WebKit. А WebKit в свою очередь не очень дружит с внешними источниками. Им нужен внутренний.

Разводим миры по углам.


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

Итак нам нужно:
  1. Два CSS файла с разными путями для двух разных миров.
  2. Редактировать и поддерживать стили иконок в одном месте(файле).
  3. Иметь один источник декларации форм иконок и фильтров — один SVG документ.
  4. Не задумываться об этом в дальнейшем при разработке.

Приступим. В решении этих задач нам помогут препроцессоры. В качестве CSS препроцессора в данном примере я буду использовать Stylus, который незаслуженно обделен вниманием русскоязычного сообщества разработчиков, но максимально просто и наглядно проиллюстрирует данный пример.
В качестве HTML препроцессора я буду использовать PHP, самый массовый препроцессор и серверный язык одновременно. Любой разработчик, пользовавшийся когда-нибудь препроцессором запросто напишет этот пример для своего любимого инструмента. Главное принцип.

Давайте переименуем файл index.html в index.php. После этого создадим папку /css и разместим в ней файл icons.svg, куда перенесем наш большой SVG документ с декларациями форм иконок.



На месте большого SVG документа в index.php напишем PHP выражение
<?php include "css/icons.svg"; ?>
которое включает текст файла icons.svg на то место, где встречается само выражение.
Пункт 3 выполнен, можно вычеркивать.

Теперь пункт 1 и 2.

В каталоге /css создадим файл icons.styl. Это будет именно то одно место, где мы будем редактировать стили наших иконок. Переместим в него все содержимое файла main.css и оформим в виде миксина icons_mixin:
icons_mixin( path = '' ) 

     .heart-icon rect{
          clip-path:url( path + '#heart-path');
     }

     .play-icon rect{
          clip-path:url( path + '#play-path');
     }


     .icon{
          width:32px;
          height:32px;
          cursor:pointer;
          fill: #ccc;

          g{
               filter:url( path + '#inset-shadow')
          }

          &:hover{
               fill: #999;
          }

          &:active, &.checked{
               fill:red;
          }
            
     }

Миксин принимает в качестве параметра путь к SVG формам. Его мы будем использовать при формировании путей для разных браузеров.
CSS стили иконок нисколько не изменились, я просто добавил нестинг для удобства и наглядности примера.

Теперь создадим еще 2 .styl файла. webkit_ie.styl и ff_op.styl.
Первый будем использовать на нашем вебсайте по умолчанию, второй только для Mozilla и Opera.

В файл webkit_ie.styl добавим:
@import 'icons.styl'

icons_mixin()

Импортируем файл с миксином иконок и выполняем его без параметров.
В файл ff_op.styl добавим:
@import 'icons.styl'

icons_mixin('icons.svg')

Импортируем файл с миксином иконок и передаем ему путь к icons.svg.

Пункт 1 и 2 выполнены. Вычеркиваем.

Если запутались, вот так выглядит проект:


В index.php поправим путь к стилям иконок, теперь это
<link rel="stylesheet" href="css/webkit_ie.css">

И в самом конце, перед закрывающимся тегом </body> добавим скрипт для условного добавления стилей иконок для браузеров с движком отличным от WebKit или Trident(IE):

<script type="text/javascript">

var firefox = navigator.userAgent.indexOf("Firefox") != -1 ;
var opera = navigator.userAgent.indexOf("Opera") != -1 ;

if ( firefox || opera ) {
  document.write('<link rel="stylesheet" href="css/ff_op.css">');
}

</script>

Вот и все. исходники

Что же мы наделали!?


Давайте оглянемся назад и посмотрим, что натворили. Несмотря на то, что с первого взгляда, система может показаться слишком комплексной, мне кажется, она получилась достаточно гибкая и простая (с моего второго взгляда) чтобы иметь право на жизнь.
  • Она полностью управляется с помощью CSS.
  • Она может работать в
    • IE 9-10
    • Mozilla 4+
    • Opera 11.6+
    • Safari 5.1+
    • Chrome 14+ (я думаяю и 4+, но проверял только до этой версии)
  • Она достаточно легка по весу. (Вспомним иконку плей <polygon fill-rule=«evenodd» clip-rule=«evenodd» points=«0,0 512,256 0,512» /> вес ее составляет всего 85 байт)
  • Все иконки лежат в одном файле, так что мы не будем сервер доставать запросами.

Все задачи выполнены. Здесь с Вашего позволения я и остановлюсь. Всем большое спасибо за внимание и время, надеюсь, Вы провели его с пользой.
Олег Соломка @LegoMushroom
карма
32,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Дизайн

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

  • +4
    Мне ещё нравится, что к элементам в SVG можно обращаться через jQuery, если у них указан класс. Можно, например, изменить кривизну линий у кривых Безье, поменяв один параметр. SVG очень недооценён и, как всегда, благодаря отсутствия поддержки у IE8.
    • +3
      мне кажется, скоро все изменится.
      9to5google.com/2012/09/15/google-drops-ie8-support-in-apps-on-november-15th/

      хотя как только в CSS появятся фигуры вроде path, SVG умрет как что-то инородное в браузере.
      • 0
        Было бы жестоко если Гугл на странице поиска поставил уведомление что Ие8 сакс, нужно обновить до хрома 28.
        Они бы таким образом давно уже заевовали рынок )))

        Или хотя бы чтобы МС не привязывали ИЕ9 к Windows 7 как к минимальной ОС…
        • 0
          Если смотреть статистику браузеров по сша — все не так радужно. Так что гуглу есть над чем стараться.
          • 0
            Гугл если не задает, то сильно влияет на эту статистику в США. Это всего лишь звоночек.
  • 0
    А что с производительностью?
    • 0
      Проблем замечено не было. Специальных тестов не производилось.
    • 0
      На таких маленьких примерах проблем не будет. Более того, мне кажется для сложной графики svg очень хорош: прикручивали библиотечку для деловой графики на основе envision, закидывали в нее порядка полумиллиона значений для графика — и отрисовывал он это с замечательнейшей скоростью.
      • 0
        Проблем замечено не было на достаточно большом проекте. Просто уточнил.
  • 0
    ( демо не работает в Опере 12.12 (win)
  • 0
    А что если всё подключать яваскриптом? Инклудить иконочный файл в каждую страницу, на мой взгляд, не самая лучшая идея.
    • 0
      Можно и JS, но пока не особо представляю как это сделать. Object, Embed и iFrame не прокатит. Совсем забыл про статичные сайты. Последние, наверное, 5 моих проектов были динамическими, всего 1 html файл. Прошу простить. Обезательно подумаю о решении.
      • 0
        $(function(){$('<div>').appendTo('body').load('/svgg.svg');});

        Это навскидку. Тут же можете прикрепить альтернативные стили, если нужна поддержка браузеров без SVG.
        • 0
          Да, действительно, спасибо! Сразу не подумал. Нужно сделать тест.
  • 0
    А что XML declaration и doctype делает внутри body??
    • 0
      Очевидно скопировалось от «вредного старичка». Что так, что без этого добра — однофункционально.
      PS: хотя как раз в ИЕ не проверял.
  • 0
    Решил попробовать использовать SVG в живом проекте и тут же столкнулся с проблемой. Проблема воспроизводится легко и в данном примере. Подскажите, пожалуйста, это нормально, что если указать размер иконки в процентах (http://codepen.io/anon/pen/iAnKu), то в FireFox (18.0.1) svg-иконка имеет размер примерно в 1,5 — 2 раза больше, чем в Safari (6.0.2)? Или это я что-то путаю?
    • +1
      Дело в том, что пример совсем «сырой» и не содержит ни ресетов, ни нормалайзов для браузеров.

      body, html{
        width:100%;
        height:100%;
      }
      

      codepen.io/anon/pen/LEjis

      А зачем вам проценты для выражения размера иконок? Или это будут не иконки? Спасибо

      • 0
        Готовлю сайт, который бы масштабировался адекватно. Собственно, в коде выглядит все максимально просто:


        Использую reset.css (http://meyerweb.com/eric/tools/css/reset/). Блокам html и body задаю 100% ширины и высоты. Буду разбираться с кодом. Возможно проблема в чем-то другом. В Safai, собственно, SVG занимает положенное место. Но сама графика не занимает это место полностью.
      • 0
        Извиняюсь:

            <object class="bHeadLogo" data='/images/logo.svg'>  
                     <img src='/images/logo.png'/>  
            </object> 
        


        И, похоже, я понял, в чем же дело. FireFox пропорционально растягивает SVG по высоте относительно заданной в процентах ширины. А Safari — нет.
  • 0
    Можете порекомендовать какой-нибудь модуль для Гранта или Галпа для автоматизации склейки общего СВГ именно с таким способом объявления фигур, как у вас?
    А включение его в страницу поможет упростить этот способ.
    • 0
      Попробуйте grunt-svgstore
      • 0
        Как я понял, он оптимален при использовании другой техники, с помощью use. Хотелось бы, чтобы получался «спрайт» именно такой, как у вас. Может, как-то этот плагин специально настроить надо?
        • 0
          Ой, прошу прощения. Запутался в техниках :) Нет таких плагинов не встречал. Честно сказать, эксперименты с техникой описанной здесь привели меня к технике с использованием (которую Крис собственно и описал — первый комментарий по вашей ссылке) и вам советую вам переходить на нее :)
          • 0
            Ясно :)
            Мне ваша понравилась тем, что можно задавать фигуру пиктограммы прямо из стилей, а use принуждает задавать её в разметке. Хорошо, буду смотреть в ту сторону.
            • 0
              Да это огромный плюс, но количество хаков и глюков в итоге перевешивают.
              Вы не поняли, они обе мои, поэтому я и запутался :)
              • 0
                Ой! Теперь буду знать.
                Спасибо вам за них! :)

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