Разработчики часто вставляют SVG непосредственно в JSX. Это удобно в использовании, но увеличивает размер пакета JS. В поисках оптимизации я решил найти другой способ использования SVG-значков, не загромождая пакет. Мы поговорим о SVG-спрайтах, что это такое, как их использовать и какие инструменты доступны для работы с ними.
Начав с теории, мы напишем скрипт, который шаг за шагом генерирует SVG-спрайт, и закончим обсуждением плагинов для Vite и Webpack .
Спрайт изображения — это набор изображений, помещенных в одно изображение. В свою очередь, 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-спрайт в отдельный файл, чтобы не загромождать файл index.html
. Сначала создайте файл sprite.svg
и поместите в него спрайт SVG. Следующий шаг — предоставить доступ к значку с помощью атрибута href
в <use/>
:
Чтобы сэкономить много времени на использовании иконок, давайте настроим автоматизацию этого процесса. Чтобы получить легкий доступ к иконкам и управлять ими так, как нам хочется, их приходится разделить, каждую в отдельный файл.
Во-первых, нам следует поместить все значки в одну папку, например:
Теперь давайте напишем скрипт, который захватывает эти файлы и объединяет их в один спрайт SVG.
Создайте файл generateSvgSprite.ts
в корневом каталоге вашего проекта.
Установите библиотеку glob :
npm i -D glob
Получите массив полных путей для каждого значка с помощью globSync
:
Теперь мы пройдемся по каждому пути к файлу и получим содержимое файла, используя встроенную библиотеку Node fs :
Отлично, у нас есть SVG-код каждой иконки, и теперь мы можем их объединить, но нам нужно заменить тег svg
внутри каждой иконки тегом symbol
и удалить ненужные атрибуты SVG.
Нам следует проанализировать наш код SVG с помощью какой-нибудь библиотеки анализатора HTML, чтобы получить его представление DOM. Я буду использовать node-html-parser :
Мы проанализировали код SVG и получили элемент SVG, как если бы это был настоящий элемент HTML.
Используя тот же парсер, создайте пустой элемент symbol
, чтобы переместить дочерние элементы svgElement
в symbol
:
После извлечения дочерних элементов из svgElement
мы также должны получить из него атрибуты id
и viewBox
. В качестве id
зададим имя файла значка.
Теперь у нас есть элемент symbol
, который можно поместить в спрайт SVG. Итак, просто определите переменную symbols
перед итерацией файлов, преобразуйте symbolElement
в строку и поместите ее в symbols
:
Последний шаг — создание самого спрайта 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-спрайты на ходу:
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', }), ], });
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" />; };
Итак, резюмируя все, чтобы избежать большого количества ручной работы с иконками, мы:
Эффективность разработки — это не только экономия времени; речь идет о раскрытии нашего творческого потенциала. Автоматизация таких мельчайших задач, как управление значками, — это не просто ярлык; это путь к более плавному и эффективному программированию. А сэкономив время на таких рутинных вещах, вы сможете сосредоточиться на более сложных задачах и быстрее расти как разработчик.