paint-brush
Как создать SVG-спрайт с иконкамик@gmakarov
3,193 чтения
3,193 чтения

Как создать SVG-спрайт с иконками

к German Makarov10m2023/12/23
Read on Terminal Reader

Слишком долго; Читать

Разработчики часто вставляют SVG непосредственно в JSX. Это удобно в использовании, но увеличивает размер пакета JS. В поисках оптимизации я решил найти другой способ использования SVG-значков, не загромождая пакет. Мы поговорим о SVG-спрайтах, что это такое, как их использовать и какие инструменты доступны для работы с ними. Начав с теории, мы напишем скрипт, который шаг за шагом генерирует спрайт SVG, и закончим обсуждением плагинов для Vite и Webpack.
featured image - Как создать SVG-спрайт с иконками
German Makarov HackerNoon profile picture

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


Начав с теории, мы напишем скрипт, который шаг за шагом генерирует SVG-спрайт, и закончим обсуждением плагинов для Vite и Webpack .

Что такое SVG-спрайт?

Спрайт изображения — это набор изображений, помещенных в одно изображение. В свою очередь, SVG Sprite представляет собой коллекцию SVG-контента, завернутую в <symbol /> , который помещается в <svg /> .


Например, у нас есть простой значок пера в формате SVG:


Чтобы получить спрайт SVG, мы заменим тег <svg /> на <symbol /> и обернем его внешне <svg /> :

Теперь это SVG-спрайт, и внутри у нас есть значок с id="icon-pen" .


Хорошо, но нам нужно придумать, как разместить этот значок на нашей HTML-странице. Мы будем использовать тег <use /> с атрибутом href , указывающим идентификатор значка, и он будет дублировать этот элемент внутри SVG.


Давайте посмотрим на пример того, как работает <use /> :

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


Давайте вернемся к нашему SVG-спрайту. Вместе с использованием <use /> мы получим следующее:

Здесь у нас есть кнопка со значком пера.


До сих пор мы использовали значок без его кода в <button /> . Если у нас на странице более одной кнопки, это не повлияет на размер нашего HTML-макета более одного раза, поскольку все значки будут взяты из нашего спрайта SVG и их можно будет использовать повторно.

Создание файла спрайта SVG

Давайте переместим наш SVG-спрайт в отдельный файл, чтобы не загромождать файл index.html . Сначала создайте файл sprite.svg и поместите в него спрайт SVG. Следующий шаг — предоставить доступ к значку с помощью атрибута href в <use/> :

Автоматизация создания SVG-спрайтов

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


Во-первых, нам следует поместить все значки в одну папку, например:

Теперь давайте напишем скрипт, который захватывает эти файлы и объединяет их в один спрайт SVG.


  1. Создайте файл generateSvgSprite.ts в корневом каталоге вашего проекта.


  2. Установите библиотеку glob :

     npm i -D glob


  3. Получите массив полных путей для каждого значка с помощью globSync :

  4. Теперь мы пройдемся по каждому пути к файлу и получим содержимое файла, используя встроенную библиотеку Node fs :

    Отлично, у нас есть SVG-код каждой иконки, и теперь мы можем их объединить, но нам нужно заменить тег svg внутри каждой иконки тегом symbol и удалить ненужные атрибуты SVG.


  5. Нам следует проанализировать наш код SVG с помощью какой-нибудь библиотеки анализатора HTML, чтобы получить его представление DOM. Я буду использовать node-html-parser :

    Мы проанализировали код SVG и получили элемент SVG, как если бы это был настоящий элемент HTML.


  6. Используя тот же парсер, создайте пустой элемент symbol , чтобы переместить дочерние элементы svgElement в symbol :

  7. После извлечения дочерних элементов из svgElement мы также должны получить из него атрибуты id и viewBox . В качестве id зададим имя файла значка.

  8. Теперь у нас есть элемент symbol , который можно поместить в спрайт SVG. Итак, просто определите переменную symbols перед итерацией файлов, преобразуйте symbolElement в строку и поместите ее в symbols :

  9. Последний шаг — создание самого спрайта SVG. Он представляет собой строку с svg в «корне» и символами в качестве дочерних элементов:

     const svgSprite = `<svg>${symbols.join('')}</svg>`;


    А если вы не планируете использовать плагины, о которых я расскажу ниже, вам нужно поместить файл с созданным спрайтом в какую-нибудь статическую папку. Большинство сборщиков используют public папку:

     fs.writeFileSync('public/sprite.svg', svgSprite);


И вот оно; скрипт готов к использованию:

 // generateSvgSprite.ts import { globSync } from 'glob'; import fs from 'fs'; import { HTMLElement, parse } from 'node-html-parser'; import path from 'path'; const svgFiles = globSync('src/icons/*.svg'); const symbols: string[] = []; svgFiles.forEach(file => { const code = fs.readFileSync(file, 'utf-8'); const svgElement = parse(code).querySelector('svg') as HTMLElement; const symbolElement = parse('<symbol/>').querySelector('symbol') as HTMLElement; const fileName = path.basename(file, '.svg'); svgElement.childNodes.forEach(child => symbolElement.appendChild(child)); symbolElement.setAttribute('id', fileName); if (svgElement.attributes.viewBox) { symbolElement.setAttribute('viewBox', svgElement.attributes.viewBox); } symbols.push(symbolElement.toString()); }); const svgSprite = `<svg>${symbols.join('')}</svg>`; fs.writeFileSync('public/sprite.svg', svgSprite);


Вы можете поместить этот скрипт в корень вашего проекта и запустить его с помощью tsx :

 npx tsx generateSvgSprite.ts


На самом деле, я использую здесь tsx , потому что раньше я везде писал код на TypeScript, а эта библиотека позволяет выполнять скрипты узлов, написанные на TypeScript. Если вы хотите использовать чистый JavaScript, вы можете запустить его с помощью:

 node generateSvgSprite.js


Итак, подведем итоги того, что делает скрипт:

  • Он просматривает папку src/icons на наличие любых файлов .svg .


  • Он извлекает содержимое каждого значка и создает из него элемент символа.


  • Он объединяет все символы в один <svg />.


  • Он создает файл sprite.svg в public папке.

Как изменить цвета значков

Давайте рассмотрим один частый и важный случай: цвета! Мы создали скрипт, в котором иконка переходит в спрайт, но на протяжении всего проекта эта иконка может иметь разные цвета.


Следует иметь в виду, что не только элементы <svg/> могут иметь атрибуты заливки или обводки, но также path , circle , line и другие. Есть очень полезная функция CSS, которая нам поможет — currentcolor .


Это ключевое слово представляет значение свойства цвета элемента. Например, если мы используем color: red для элемента, имеющего background: currentcolor , то этот элемент будет иметь красный фон.


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


Это оптимизатор SVG, который может помочь не только с цветами, но и с удалением избыточной информации из SVG.


Давайте установим svgo :

 npm i -D svgo


svgo имеет встроенные плагины, и один из них — convertColors , у которого есть свойство currentColor: true . Если мы используем этот вывод SVG, он заменит цвета на currentcolor . Вот использование svgo вместе с convertColors :

 import { optimize } from 'svgo'; const output = optimize( '<svg viewBox="0 0 24 24"><path fill="#000" d="m15 5 4 4" /></svg>', { plugins: [ { name: 'convertColors', params: { currentColor: true, }, } ], } ) console.log(output);


И вывод будет:

 <svg viewBox="0 0 24 24"><path fill="currentColor" d="m15 5 4 4"/></svg>


Давайте добавим svgo в наш волшебный скрипт, который мы написали в предыдущей части:

 // generateSvgSprite.ts import { globSync } from 'glob'; import fs from 'fs'; import { HTMLElement, parse } from 'node-html-parser'; import path from 'path'; import { Config as SVGOConfig, optimize } from 'svgo'; // import `optimize` function const svgoConfig: SVGOConfig = { plugins: [ { name: 'convertColors', params: { currentColor: true, }, } ], }; const svgFiles = globSync('src/icons/*.svg'); const symbols: string[] = []; svgFiles.forEach(file => { const code = fs.readFileSync(file, 'utf-8'); const result = optimize(code, svgoConfig).data; // here goes `svgo` magic with optimization const svgElement = parse(result).querySelector('svg') as HTMLElement; const symbolElement = parse('<symbol/>').querySelector('symbol') as HTMLElement; const fileName = path.basename(file, '.svg'); svgElement.childNodes.forEach(child => symbolElement.appendChild(child)); symbolElement.setAttribute('id', fileName); if (svgElement.attributes.viewBox) { symbolElement.setAttribute('viewBox', svgElement.attributes.viewBox); } symbols.push(symbolElement.toString()); }); const svgSprite = `<svg xmlns="http://www.w3.org/2000/svg">${symbols.join('')}</svg>`; fs.writeFileSync('public/sprite.svg', svgSprite);


И запустите скрипт:

 npx tsx generateSvgSprite.ts


В результате спрайт SVG будет содержать значки с currentColor . И эти значки можно использовать везде в проекте, любого цвета.

Плагины

У нас есть скрипт, и мы можем запустить его, когда захотим, но немного неудобно использовать его вручную. Итак, я рекомендую несколько плагинов, которые могут просматривать наши .svg файлы и генерировать SVG-спрайты на ходу:


  1. vite-plugin-svg-spritemap (для пользователей vite )

    Это мой плагин, который содержит в основном скрипт, который мы только что создали в этой статье. По умолчанию в плагине включена замена currentColor , поэтому вы можете легко его настроить.

     // vite.config.ts import svgSpritemap from 'vite-plugin-svg-spritemap'; export default defineConfig({ plugins: [ svgSpritemap({ pattern: 'src/icons/*.svg', filename: 'sprite.svg', }), ], });


  2. svg-spritemap-webpack-plugin (для пользователей веб-пакета )

    Я использовал этот плагин Webpack, пока не перешел на Vite. Но этот плагин по-прежнему является хорошим решением, если вы используете Webpack. Вам следует вручную включить преобразование цветов, и это будет выглядеть так:

     // webpack.config.js const SVGSpritemapPlugin = require('svg-spritemap-webpack-plugin'); module.exports = { plugins: [ new SVGSpritemapPlugin('src/icons/*.svg', { output: { svgo: { plugins: [ { name: 'convertColors', params: { currentColor: true, }, }, ], }, filename: 'sprite.svg', }, }), ], }

Использование в макете

Я приведу пример на React , но вы можете реализовать его там, где захотите, потому что в основном он касается HTML. Итак, поскольку у нас есть sprite.svg в папке сборки, мы можем получить доступ к файлу спрайта и создать базовый компонент Icon :

 const Icon: FC<{ name: string }> = ({ name }) => ( <svg> <use href={`/sprite.svg#${name}`} /> </svg> ); const App = () => { return <Icon name="pen" />; };

Окончательный результат

Итак, резюмируя все, чтобы избежать большого количества ручной работы с иконками, мы:

  • можно легко сохранять и систематизировать значки в проекте с желаемыми именами.


  • иметь скрипт, который объединяет все значки в один спрайт в отдельном файле, что уменьшает размер пакета и позволяет нам использовать эти значки в любом месте проекта.


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


  • иметь плагин, который может просматривать наши файлы значков и генерировать спрайты на ходу в рамках процесса сборки.


  • иметь компонент Icon, который является вишенкой на торте

Заключение

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