Frontender Magazine

Grunticon

Пиксельная графика умрет, потому что при высоком разрешении экрана один пиксель будет незаметен. «Пикселизированные» картинки будут нужны не чаще, чем мозаика из кафельных плиток. Артемий Лебедев

Векторная графика медленно, но верно приходит в веб. Распространение дисплеев с высокой плотностью пикселей («ретина» в терминологии Apple), а также общее упрощение интерфейсов и поддержка форматов векторной графики делают её не только возможной, но и весьма удобной. Работая с вектором, вам не нужно готовить несколько версий изображений для разных устройств, а ваша графика легко масштабируется и навсегда останется совместимой с дисплеями любого разрешения.

Есть несколько вариантов использования вектора в браузере:

  1. при помощи веб-шрифтов;
  2. используя библиотеку raphael.js, которая позволяет создавать векторную графику, используя JavaScript, применяя для этого SVG или VML в зависимости от поддержки этих технологий браузером;
  3. используя SVG напрямую: вставляя в разметку при помощи тега <img>;
  4. используя как фоны в CSS;
  5. непосредственно выводя в HTML.

У каждого из вариантов есть свои преимущества и недостатки. Веб-шрифты, к примеру, нельзя использовать в качестве фонов (background-image). Существенным минусом SVG может оказаться недостаточная поддержка этого формата браузерами: Internet Explorer версии ниже 9.0 и Android Browser ниже версии 3.0 не умеют отображать графику в этом формате. Пренебречь этими браузерами возможно не всегда, поэтому приходится задумываться о деградации графики до растровой.

Библиотека grunticon призвана решить проблему обратной совместимости и генерации растровой графики для устаревших браузеров. Grunticon — это задача для Grunt.js, которая берёт в качестве источника папку с SVG-файлами и на её основании создаёт 3 CSS-файла:

  1. основной файл с SVG-файлами в виде data:url фонов;
  2. аналогичный файл с data:url фонами в формате PNG;
  3. файл для браузеров, не поддерживающих data:url, где в виде фонов прописаны отдельные png-файлы.

Естественно, grunticon создаёт и сами png-файлы, для чего использует PhantomJS — безинтерфейсный Webkit, который и отвечает за конвертацию векторной графики в растровую.

Утилита также создаёт небольшой JavaScript-файл со сниппетом, который определяет возможности браузера и подключает тот или иной CSS-файл. Данный сниппет (по умолчанию он находится в файле grunticon.loader.txt) необходимо добавить в шапку документа, либо во внешний JS-файл (не забыв оставить в шапке содержимое тега <noscript>).

Даже если ваш сайт или приложение не поддерживает старые браузеры, Grunticon всё ещё может быть весьма полезным — для SVG-файлов он делает по сути то же, что и спрайты для растровой графики: собирает их в 1 файл, тем самым уменьшая количество запросов и увеличивая скорость отрисовки интерфейса. В этом случае вы можете не использовать JavaScript-сниппет, а подключать файл с SVG в виде data:url (по умолчанию он называется icons.data.svg.css), используя тег <link>, либо вовсе включая его содержимое в ваш основной CSS-файл — это вполне оправдано на небольших проектах и легко автоматизируется (например, при помощи grunt-contrib-concat).

Для установки grunticon вам потребуется установленный Grunt, который в свою очередь требует Node.js. Если эти требования удовлетворены, выполните команду npm install grunt-grunticon. Она установит необходимые компоненты, после чего в файл с настройками задач Gruntfile.js нужно добавить строку:

grunt.loadNpmTasks('grunt-grunticon');

а также настроить grunticon — указать, в какой папке искать исходные векторные файлы, в какую папку сохранять CSS и PNG-файлы, какой префикс использовать в названии классов и др. Полный список настроек можно найти в тестовом Gruntfile.js на Github, ниже приведён пример базовой настройки:

grunt.initConfig({
    grunticon: {
        foo: {
            options: {
                // это единственные обязательны настройки:
                // src: папка, в которой расположены SVG-файлы
                src: "example/source/",
                // dest: папка, в которую будут записаны CSS-файлы и графика в PNG
                dest: "example/output/",

                // использовать ли сжатие SVGO. По умолчанию отключено
                svgo: true,

                // использовать ли сжатие в PNG, по умолчанию включено
                pngcrush: true,

                // Имена CSS-файлов
                datasvgcss: "icons.data.svg.css",
                datapngcss: "icons.data.png.css",
                urlpngcss: "icons.fallback.css",

                // Имя HTML-файла с предварительным просмотром всех иконок
                previewhtml: "preview.html",

                // имя файла с JavaScript-сниппетом
                // его содержимое необходимо вручную вставить в ваши шаблоны
                loadersnippet: "grunticon.loader.txt",

                // имя папки, в которую будут записаны PNG
                pngfolder: "png",

                // префикс для CSS-классов
                cssprefix: "icon-",

                // ширина и высота по умолчанию
                // если эти значения не заданы, то ширина и высота не задаются, вам нужно вручную указывать их в основном CSS
                defaultWidth: "300px",
                defaultHeight: "200px",
                // если вам не нравится система, при которой классы создаются на основе файлов,
                // вы можете переопределить, какие селекторы
                // будут сгенерированы для того или иного файла
                // (имя файла пишется без расширения)
                customselectors: {
                    "cat" : "#el-gato",
                    "gummy-bears-2" : "nav li a.deadly-bears:before",
                    "coolicon": ".coolicon_one, .coolicon_two, .coolicon *"
                }

            }
        }
    }
});

Если grunticon — единственная задача Grunt, которую вы используете, можно установить её задачей по умолчанию, добавив строку: grunt.registerTask('default', 'grunticon');.

После настройки необходимо в папке проекта запустить команду grunt, которая создаст или перезапишет все необходимые файлы.

В создаваемых утилитой CSS-файлах имена классов основываются на именах файлов. По умолчанию к имени файла добавляется префикс icon-, для файла file.svg создаётся класс .icon-file. Префикс можно изменить или вовсе убрать в настройках. Таким образом, чтобы при стандартных настройках использовать фон из получившегося CSS-файла, нужно к элементу добавить класс icon-file. По умолчанию значение background-repeat равно none, так что если вы хотите сделать повторяющийся фон, это нужно отдельно прописать в вашем основном CSS-файле:

.icon-file {background-repeat: repeat;}

Править сгенерированные grunticon файлы нет смысла, так как они будут перезаписаны при следующем запуске команды.

Существует версия grunticon для SASS, которая, правда, не обновлялась уже почти год и несовместима с текущей версией grunt.

Не так давно grunticon стал доступен в виде сайта Grumpicon, который представляет собой драг-н-дроп интерфейс к утилите для тех, кто не готов вникать в тонкости настройки задач и работу с командной строкой.

Cкриншот

Если вы заметили ошибку, вы всегда можете отредактировать статью, создать issue или просто написать об этом Антону Немцеву в skype ravencry.

Глеб Калинин

Комментарии (8 комментариев, если быть точным)

Автар пользователя
h4
  1. grunt-icon наверно имеет смысл ставить глобально? Или, в крайнем случае, прописать его как dev-зависимость.
  2. noscript-то зачем откапывать?
Автар пользователя
smolnikov

Мне кажется, нет ничего плохого в том, чтобы ставить его локально. А вот использовать npm install grunt-grunticon --save-dev, чтобы сохранить в зависимости, действительно, стоит.

Автар пользователя
h4

@smolnikov плохого ничего нет, но лучше все системные инструменты держать доступными из любого места на диске. Всякие там csso, grunt, uglify.

Автар пользователя
smolnikov

Хммм, вот сейчас задумался. С одной стороны, да, всякие csso, svgo, grunt, etc. стоят глобально. А вот таски для grunt всегда ставил локально, добавлял в dependencies и благополучно гитигнорил node_modules. А, ну да — при npm install он же поставит все из package.json локально, нет?

Автар пользователя
h4

Вот не знаю, как решается ситуация, когда у нас что-то стоит глобально и это же прописано в package.json.

Автар пользователя
glebis

Насколько я помню, локальная установка — это рекомендуемая процедура. Причина: компоненты могут в любой момент обновиться и не предоставить обратной совместимости. Лично с этим уже сталкивался. Самый большой пример — сам grunt (впрочем, его как раз я ставлю глобально, хотя можно было бы и его локально, для удобства используя smartcd). Чтобы не ломать потом голову в ситуации, где у тебя на одном проекте используется версия, условно, 0.9.3, на другом 0.9.4, друг с другом они уже не совместимы, а глобально стоит 0.9.4.

Автар пользователя
sapegin

Всё поставится локально. Возможно, npm достанет что-то из своего кэша, если версии совпадут. Более того, бинарники, установленные локально, имеют преимущество перед глобальными, если использовать их в скриптах внутри package.json.

Я за установку любых зависимостей локально и фиксацию версий в package.json. И Гранта тоже. grunt-cli сам вызовет всё, что нужно.

Автар пользователя
sapegin

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