Статьи

Рабочий рабочий процесс SVG для доступных значков

Принято считать, что использование шрифтов для иконок — плохая практика. Это плохо для рендеринга (изменения размера, позиционирования и т. Д.), Имеет странные сбои (прокси-браузеры, CORS и т. Д.), Передает плохую семантику, может ухудшить доступность, и этот список можно продолжить. У CSS-Tricks есть проницательная часть по этому вопросу .

При этом я всегда нахожу немного сложным иметь дело с SVG. Давайте будем честными, я не Сара Суидан . SVG не совсем моя вещь. Все эти пути, странные теги и атрибуты, тьфу. Я действительно не знаю, как их использовать. Тем не менее, я должен. И все же я люблю делать вещи правильно.

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

В чем суть?

У CSS-Tricks есть очень хорошая статья Криса Койера о том, как построить систему иконок с SVG-спрайтами. Учитывая, что эта статья является прямым применением статьи Криса, я предлагаю вам прочитать ее, если вы еще этого не сделали.

Вот примерно как это работает.

  1. Исходные иконки собраны в отдельные файлы SVG в отдельной папке.
  2. Спрайт генерируется с помощью спрайта .
  3. Спрайт включен в основной макет для дальнейшего использования.
  4. Иконки отображаются через небольшой компонент.
  5. ???
  6. Прибыль.

Настройка немного зависит от типа проекта, над которым вы работаете (Jekyll, React, Rails …), но суть остается неизменной. В этой статье я проведу вас по шагам, чтобы вы могли начать использовать его сегодня.

Сбор файлов значков

Если вы спросите меня, это довольно сложная часть, потому что иметь правильные файлы SVG — непростая тема. В зависимости от того, какой инструмент используется для экспорта иконки, разметка выглядит по-разному, может быть раздутой и так далее. И не viewBox меня на проблемы viewBox

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

Иконы Icomoon

Сначала выберите нужные вам иконки. Когда вы закончите, нажмите «Создать SVG & More». Вы должны увидеть в списке все иконки, которые вы выбрали. Теперь вы можете скачать пакет от Icomoon. Сгенерированный zip-файл содержит все необходимое для выбранных вами значков (PNG-файлы, SVG-файлы, CSS, JavaScript, демо…). Я рекомендую скопировать содержимое подпапки SVG (содержащей отдельные файлы значков) и вставить ее в папку значков.

Прежде чем двигаться дальше, вам придется немного почистить файлы SVG. Это не совсем необходимо само по себе, но я бы порекомендовал вам сделать это, чтобы избежать раздувания вашего SVG-спрайта с большим количеством мусора. Вот как выглядит иконка огня от Icomoon при загрузке:

 <?xml version="1.0" encoding="utf-8"?> <!-- Generated by IcoMoon.io --> <!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" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16"> <path d="M5.016 16c-1.066-2.219-0.498-3.49 0.321-4.688 0.897-1.312 1.129-2.61 1.129-2.61s0.706 0.917 0.423 2.352c1.246-1.387 1.482-3.598 1.293-4.445 2.817 1.969 4.021 6.232 2.399 9.392 8.631-4.883 2.147-12.19 1.018-13.013 0.376 0.823 0.448 2.216-0.313 2.893-1.287-4.879-4.468-5.879-4.468-5.879 0.376 2.516-1.364 5.268-3.042 7.324-0.059-1.003-0.122-1.696-0.649-2.656-0.118 1.823-1.511 3.309-1.889 5.135-0.511 2.473 0.383 4.284 3.777 6.197z"></path> </svg> 

Так много для <path> ! Я рекомендую вам удалить определение XML, начальный комментарий, тип документа и оболочку SVG (потому что мы вставим SVG, ссылаясь на спрайт), так что останется только фактический нарисованный контент:

 <path d="M5.016 16c-1.066-2.219-0.498-3.49 0.321-4.688 0.897-1.312 1.129-2.61 1.129-2.61s0.706 0.917 0.423 2.352c1.246-1.387 1.482-3.598 1.293-4.445 2.817 1.969 4.021 6.232 2.399 9.392 8.631-4.883 2.147-12.19 1.018-13.013 0.376 0.823 0.448 2.216-0.313 2.893-1.287-4.879-4.468-5.879-4.468-5.879 0.376 2.516-1.364 5.268-3.042 7.324-0.059-1.003-0.122-1.696-0.649-2.656-0.118 1.823-1.511 3.309-1.889 5.135-0.511 2.473 0.383 4.284 3.777 6.197z"></path> 

Генерация Спрайта

Вы можете спросить себя, почему мы не используем спрайт, сгенерированный Icomoon напрямую ( symbol-defs.svg )? На самом деле, мы могли бы. Вы увидите, что мы собираемся сделать то же самое. Причины, по которым я не люблю использовать спрайт:

  • Icomoon добавляет несколько вещей, которые не нужны (в основном атрибуты) и не всегда актуальны (например, <title> заполняется после имени файла).
  • Я не хочу возвращаться в Icomoon, чтобы загружать спрайт снова каждый раз, когда я хочу добавить значок в спрайт. Приятно иметь систему внутри проекта.

Существует несколько способов создания спрайта из папки файлов значков, но большинство из них используют какой-то конвейер ресурсов, такой как Grunt или Gulp. Вот почему я создал spritesh , скрипт Bash, который выполняет Just That ™.

Примечание: если у вас есть генератор спрайтов, который вам нравится, пожалуйста, обязательно имейте это в виду. spritesh — только маленький помощник, когда вы не можете / не хотите загружать Gulp / Grunt и все зависимости только для связывания значков.

Вы можете установить spritesh через npm или как гем (оба в любом случае являются тонкими оболочками вокруг скрипта Bash):

 npm install spritesh -g 

Затем запустите спрайте на папку с иконками. Предполагая, что вы сохранили файлы значков в assets/images/icons и хотите сгенерировать спрайт в папке _includes , вот как это будет выглядеть:

 spritesh --input assets/images/icons --output _includes/sprite.svg --viewbox "0 0 16 16" --prefix icon- 

Примечание. Возможно, вы захотите поместить эту команду в скрипт npm, чтобы не вводить ее заново каждый раз, когда вы захотите восстановить спрайт.

Поскольку мы viewBox элемент <svg> (и его атрибут viewBox ) из наших исходных файлов, аргумент viewbox необходим. Здесь мы используем 0 0 16 16 потому что это то, что Icomoon использует в первую очередь.

Аргумент prefix не является обязательным. Он просто предотвращает любой конфликт с уже существующими атрибутами DOM в DOM при включении спрайта. В этом нет необходимости, но я бы сказал, что хорошей практикой является использование пространства имен для атрибута id значков.

Примечание: если вы работаете в Windows, вам нужно будет запустить spritesh в git bash или Cygwin .

Теперь у вас должен быть спрайт, похожий на этот:

 <svg xmlns="http://www.w3.org/2000/svg" style="display:none"> <symbol id='icon-fire' viewBox='0 0 16 16'><path d="M5.016 16c-1.066-2.219-0.498-3.49 0.321-4.688 0.897-1.312 1.129-2.61 1.129-2.61s0.706 0.917 0.423 2.352c1.246-1.387 1.482-3.598 1.293-4.445 2.817 1.969 4.021 6.232 2.399 9.392 8.631-4.883 2.147-12.19 1.018-13.013 0.376 0.823 0.448 2.216-0.313 2.893-1.287-4.879-4.468-5.879-4.468-5.879 0.376 2.516-1.364 5.268-3.042 7.324-0.059-1.003-0.122-1.696-0.649-2.656-0.118 1.823-1.511 3.309-1.889 5.135-0.511 2.473 0.383 4.284 3.777 6.197z"></path></symbol> <!-- Other <symbol>s… --> </svg> 

Осталось только включить спрайт в основной макет. Например, если вы работаете на веб-сайте Jekyll, это может быть так же просто, как _includes/ спрайт в папке _includes/ и добавить его в файл макета:

 {% include sprite.svg %} 

Создание компонента Icon

Пока что мы собрали иконки и сделали спрайт. Это хорошо, однако нам все еще нужно настроить удобный способ использования этих значков. Мы будем использовать <use> внутри тега <svg> чтобы ссылаться на соответствующий символ из нашего спрайта (подробнее об этой технике в этой статье о хитростях CSS), например так:

 <svg viewBox="0 0 16 16" class="icon icon-fire"> <use xlink:href="#icon-fire"></use> </svg> 

Это работает, но не очень дружелюбно и в конечном итоге довольно раздражает, когда приходится иметь дело с ним в долгосрочной перспективе. Кроме того, если нам нужно изменить класс или атрибут viewBox , нам придется viewBox все вхождения этого в проекте. Совсем не идеально.

Мы хотим абстрагировать этот фрагмент повторяющейся разметки в частичное. В Джекиле это может выглядеть так:

 <svg viewBox="0 0 16 16" class="icon icon-{{ include.icon }}"> <use xlink:href="#icon-{{ include.icon }}"></use> </svg> 

Чтобы использовать его, вы включаете частичное и передаете ему параметр icon :

 {% include icon.html icon="fire" %} 

Не стесняйтесь улучшать частичное, чтобы оно принимало другие параметры, такие как дополнительный класс.

В React это может выглядеть так:

 const Icon = (props) => ( <svg viewBox='0 0 16 16' className={`icon icon-${props.icon}`}> <use xlinkHref={`#icon-${props.icon}`} /> </svg> ); export default Icon; 

Примечание: xlinkHref доступен только согласно Реакции 0.14. В React 0.13 вам придется dangerouslySetInnerHTML использовать dangerouslySetInnerHTML . Подробнее об этом в этом ответе о переполнении стека .

А потом:

 <Icon icon='fire' /> 

Слово о доступности

В этой статье от Léonie Watson рекомендуется добавить заголовок и описание с <title> и <desc> соответственно к определениям <symbol> в спрайте, чтобы улучшить доступность.

Я полностью поддерживаю это, однако, я склонен думать, что заголовок и описание сильно зависят от контекста. Поэтому, по моему мнению, они лучше определены во время использования (в нашем компоненте), а не во время определения (в спрайте).

Например, если вы используете значок рядом с текстом, вы не хотите, чтобы заголовок читался вслух, потому что для этого уже есть текст. С другой стороны, если вы используете значок в качестве только содержимого для кнопки, вы хотите, чтобы заголовок и описание были прочитаны, чтобы пользователь знал назначение кнопки.

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

 {% capture id %}{% increment uniqueid %}{% endcapture %} <svg viewBox="0 0 16 16" role="img" class="icon icon-{{ include.icon }}" aria-labelledby="{% if include.title %}title-{{ id }}{% endif %}{% if include.desc %} desc-{{ id }}{% endif %}"> {% if include.title %} <title id="title-{{ id }}">{{ include.title }}</title> {% endif %} {% if include.desc %} <desc id="title-{{ id }}">{{ include.desc }}</desc> {% endif %} <use xlink:href="#icon-{{ include.icon }}"></use> </svg> 

Тег {% increment %} Liquid инициализирует переменную и затем {% increment %} ее на единицу при каждом вызове. В нашем сценарии он вызывается каждый раз, когда мы включаем частичный значок, то есть для каждого значка.

Версия React будет работать так же, используя Lodash для получения уникального идентификатора (не стесняйтесь использовать реализацию по вашему выбору):

 import { uniqueId } from 'lodash'; const Icon = (props) => { const id = uniqueId(); return ( <svg viewBox='0 0 16 16' role='img' className={`icon icon-${props.icon}`} aria-labelledby={ (props.title ? `title-${id}` : '') + (props.desc ? ` desc-${id}` : '') }> {props.title && <title id={`title-${id}`}>{props.title}</title>} {props.desc && <desc id={`desc-${id}`}>{props.desc}</desc>} <use xlinkHref={`#icon-${props.icon}`} /> </svg> ); } export default Icon; 

Я признаю, что это гораздо более многословно, но это не проблема, учитывая, что:

  • Роль компонента состоит в том, чтобы абстрагировать сложность и избежать повторения.
  • Доступность чрезвычайно важна и должна быть приоритетом.

Завершение дела

Эй, теперь все было не так плохо, правда? Если подвести итог, наша система позволяет легко:

  • Генерация спрайта из командной строки (что делает его легко подключаемым в любом скрипте сборки) с настраиваемыми параметрами;
  • Используйте спрайт и настройте вывод с частичным / компонентом;
  • Добавьте новые значки.

Это звучит как хорошая система, если вы спросите меня! Если мы хотим продвинуться дальше, мы можем использовать SVGO для оптимизации файлов SVG. Установите его через npm:

 npm install svgo spritesh --save-dev 

Затем хорошо используйте сценарии npm в вашем package.json :

 { "scripts": { "sprite": "spritesh --input assets/images/icons --output _includes/sprite.svg --viewbox '0 0 16 16' --prefix icon-", "presprite": "svgo assets/images/icons" }, "devDependencies": { "spritesh": "^1.0.8", "svgo": "^0.6.1" } } 

Теперь каждый раз, когда мы запускаем задачу sprite npm сначала запускает svgo в папке значков (что полезно только один раз, но может быть полезно сохранить в случае добавления новых значков).

Вероятно, есть место для дальнейших улучшений, поэтому, если вы думаете о чем-нибудь, чтобы улучшить этот рабочий процесс SVG, пожалуйста, поделитесь!

Огромная благодарность Саре Соуэдан за ее технический обзор в области SVG и Хейдону Пикерингу за его проницательное мнение о доступности.