Os desenvolvedores costumam inserir SVG diretamente no JSX. Isso é conveniente de usar, mas aumenta o tamanho do pacote JS. Na busca pela otimização, decidi encontrar outra maneira de usar ícones SVG sem sobrecarregar o pacote. Falaremos sobre sprites SVG, o que são, como usá-los e quais ferramentas estão disponíveis para trabalhar com eles.
Começando pela teoria, escreveremos um script que gera um sprite SVG passo a passo e concluiremos discutindo plugins para vite e webpack .
Um sprite de imagem é uma coleção de imagens colocadas em uma única imagem. Por sua vez, SVG Sprite é uma coleção de conteúdo SVG, agrupado em <symbol />
, que é colocado em <svg />
.
Por exemplo, temos um ícone de caneta SVG simples:
Para obter um sprite SVG, substituiremos a tag <svg />
por <symbol />
e envolveremos <svg />
externamente:
Agora é um sprite SVG e temos um ícone dentro com id="icon-pen"
.
Ok, mas devemos descobrir como colocar esse ícone em nossa página HTML. Usaremos a tag <use />
com o atributo href
, especificando o ID do ícone, e duplicará este elemento dentro do SVG.
Vamos dar uma olhada em um exemplo de como funciona <use />
:
Neste exemplo, existem dois círculos. O primeiro tem contorno azul e o segundo é uma duplicata do primeiro, mas com preenchimento vermelho.
Voltemos ao nosso sprite SVG. Junto com o uso de <use />
, obteremos isto:
Aqui temos um botão com nosso ícone de caneta.
Até agora, usamos um ícone sem seu código em <button />
. Se tivermos mais de um botão na página, isso não afetará mais de uma vez o tamanho do nosso layout HTML, pois todos os ícones virão do nosso sprite SVG e serão reutilizáveis.
Vamos mover nosso sprite SVG para um arquivo separado para que não tenhamos que sobrecarregar o arquivo index.html
. Primeiro, crie um arquivo sprite.svg
e coloque um sprite SVG nele. A próxima etapa é fornecer acesso ao ícone usando o atributo href
em <use/>
:
Para economizar muito tempo no uso de ícones, vamos configurar uma automação para esse processo. Para ter fácil acesso aos ícones e gerenciá-los como quisermos, eles devem ser separados, cada um em seu arquivo.
Primeiramente devemos colocar todos os ícones na mesma pasta, por exemplo:
Agora, vamos escrever um script que capture esses arquivos e os combine em um único sprite SVG.
Crie o arquivo generateSvgSprite.ts
no diretório raiz do seu projeto.
Instale a biblioteca glob :
npm i -D glob
Obtenha uma série de caminhos completos para cada ícone usando globSync
:
Agora, iremos iterar cada caminho de arquivo e obter o conteúdo do arquivo usando a biblioteca interna do Node fs :
Ótimo, temos o código SVG de cada ícone e agora podemos combiná-los, mas devemos substituir a tag svg
dentro de cada ícone pela tag de symbol
e remover atributos SVG inúteis.
Deveríamos analisar nosso código SVG com alguma biblioteca de analisador HTML para obter sua representação DOM. Usarei node-html-parser :
Analisamos o código SVG e obtivemos o elemento SVG como se fosse um elemento HTML real.
Usando o mesmo analisador, crie um elemento symbol
vazio para mover os filhos de svgElement
para symbol
:
Depois de extrair os filhos de svgElement
, também devemos obter os atributos id
e viewBox
dele. Como id
, vamos definir o nome do arquivo de ícone.
Agora temos um elemento symbol
que pode ser colocado em um sprite SVG. Portanto, basta definir a variável symbols
antes de iterar os arquivos, transformar o symbolElement
em uma string e colocá-lo em symbols
:
A etapa final é criar o próprio sprite SVG. Representa uma string com svg
em “root” e símbolos como filhos:
const svgSprite = `<svg>${symbols.join('')}</svg>`;
E se você não está pensando em usar plugins, dos quais falarei a seguir, você precisa colocar o arquivo com o sprite criado em alguma pasta estática. A maioria dos bundlers usa uma pasta public
:
fs.writeFileSync('public/sprite.svg', svgSprite);
E é isso; o script está pronto para uso:
// 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);
Você pode colocar este script na raiz do seu projeto e executá-lo com tsx :
npx tsx generateSvgSprite.ts
Na verdade, estou usando tsx aqui porque costumava escrever código em TypeScript em qualquer lugar, e esta biblioteca permite executar scripts de nó escritos em TypeScript. Se quiser usar JavaScript puro, você pode executá-lo com:
node generateSvgSprite.js
Então, vamos resumir o que o script está fazendo:
src/icons
qualquer arquivo .svg
.
<svg />.
sprite.svg
na pasta public
.Vamos abordar um caso frequente e importante: as cores! Criamos um script onde o ícone entra em um sprite, mas esse ícone pode ter cores diferentes ao longo do projeto.
Devemos ter em mente que não apenas os elementos <svg/>
podem ter atributos de preenchimento ou traço, mas também path
, circle
, line
e outros. Há um recurso CSS muito útil que nos ajudará - currentcolor .
Esta palavra-chave representa o valor da propriedade color de um elemento. Por exemplo, se usarmos color: red
em um elemento que possui um background: currentcolor
, então este elemento terá um fundo vermelho.
Basicamente, precisamos alterar cada valor de atributo de traço ou preenchimento para currentcolor
. Espero que você não esteja vendo isso feito manualmente, heh. E mesmo escrever algum código que substitua ou analise strings SVG não é muito eficiente em comparação com uma ferramenta muito útil svgo .
Este é um otimizador SVG que pode ajudar não apenas com cores, mas também na remoção de informações redundantes do SVG.
Vamos instalar o svgo :
npm i -D svgo
svgo
possui plug-ins integrados, e um deles é convertColors
, que possui a propriedade currentColor: true
. Se usarmos esta saída SVG, ela substituirá cores por currentcolor
. Aqui está o uso de svgo
junto com 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);
E a saída será:
<svg viewBox="0 0 24 24"><path fill="currentColor" d="m15 5 4 4"/></svg>
Vamos adicionar svgo
ao nosso script mágico que escrevemos na parte anterior:
// 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);
E execute o script:
npx tsx generateSvgSprite.ts
Como resultado, o sprite SVG conterá ícones com currentColor
. E esses ícones podem ser usados em qualquer lugar do projeto com qualquer cor que você desejar.
Temos um script e podemos executá-lo quando quisermos, mas é um pouco inconveniente usá-lo manualmente. Portanto, recomendo alguns plug-ins que podem visualizar nossos arquivos .svg
e gerar sprites SVG em qualquer lugar:
vite-plugin-svg-spritemap (para usuários vite )
Este é o meu plugin que contém basicamente este script que acabamos de criar neste artigo. O plugin tem a substituição currentColor
habilitada por padrão, então você pode configurar o plugin com bastante facilidade.
// 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 (para usuários do webpack )
Usei este plugin Webpack até mudar para o Vite. Mas este plugin ainda é uma boa solução se você estiver usando o Webpack. Você deve ativar manualmente a conversão de cores e ficará assim:
// 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', }, }), ], }
Darei um exemplo em React , mas você pode implementá-lo onde quiser porque se trata principalmente de HTML. Então, como temos sprite.svg
em nossa pasta build, podemos acessar o arquivo sprite e criar o componente Icon
básico:
const Icon: FC<{ name: string }> = ({ name }) => ( <svg> <use href={`/sprite.svg#${name}`} /> </svg> ); const App = () => { return <Icon name="pen" />; };
Então, resumindo tudo, para evitar muito trabalho manual com ícones, nós:
A eficiência no desenvolvimento não envolve apenas economia de tempo; trata-se de desbloquear nosso potencial criativo. Automatizar tarefas essenciais, como gerenciar ícones, não é apenas um atalho; é uma porta de entrada para uma experiência de codificação mais suave e impactante. E economizando tempo nessas tarefas rotineiras, você pode se concentrar em tarefas mais complexas e crescer como desenvolvedor com mais rapidez.