Los desarrolladores suelen insertar SVG directamente en JSX. Esto es cómodo de usar, pero aumenta el tamaño del paquete JS. En busca de la optimización, decidí buscar otra forma de usar íconos SVG sin saturar el paquete. Hablaremos sobre los sprites SVG, qué son, cómo usarlos y qué herramientas están disponibles para trabajar con ellos.
Comenzando con la teoría, escribiremos un script que genere un objeto SVG paso a paso y concluiremos analizando los complementos para vite y webpack .
¿Qué es SVG Sprite?
Un objeto de imagen es una colección de imágenes colocadas en una sola imagen. A su vez, SVG Sprite es una colección de contenido SVG, empaquetado en <symbol />
, que se coloca en <svg />
.
Por ejemplo, tenemos un icono de lápiz SVG simple:
Para obtener un objeto SVG, reemplazaremos la etiqueta <svg />
con <symbol />
y la envolveremos con <svg />
externamente:
Ahora es un objeto SVG y tenemos un ícono dentro con id="icon-pen"
.
Ok, pero deberíamos descubrir cómo colocar este ícono en nuestra página HTML. Usaremos la etiqueta <use />
con el atributo href
, especificando el ID del icono, y duplicará este elemento dentro de SVG.
Echemos un vistazo a un ejemplo de cómo funciona <use />
:
En este ejemplo, hay dos círculos. El primero tiene un contorno azul y el segundo es un duplicado del primero pero con un relleno rojo.
Volvamos a nuestro objeto SVG. Junto con el uso de <use />
, obtendremos esto:
Aquí tenemos un botón con el icono de nuestro bolígrafo.
Hasta ahora, hemos usado un ícono sin su código en <button />
. Si tenemos más de un botón en la página, no afectará más de una vez el tamaño de nuestro diseño HTML porque todos los íconos provendrán de nuestro objeto SVG y serán reutilizables.
Creando un archivo SVG Sprite
Movamos nuestro objeto SVG a un archivo separado para no tener que saturar el archivo index.html
. Primero, cree un archivo sprite.svg
y coloque un objeto SVG en él. El siguiente paso es proporcionar acceso al ícono usando el atributo href
en <use/>
:
Automatización de la creación de sprites SVG
Para ahorrar mucho tiempo en el uso de íconos, configuremos una automatización para este proceso. Para acceder fácilmente a los iconos y gestionarlos como queramos, es necesario separarlos, cada uno en su propio archivo.
Primero, deberíamos poner todos los íconos en la misma carpeta, por ejemplo:
Ahora, escribamos un script que tome estos archivos y los combine en un único objeto SVG.
Cree el archivo
generateSvgSprite.ts
en el directorio raíz de su proyecto.
Instalar la biblioteca global :
npm i -D glob
Obtenga una variedad de rutas completas para cada ícono usando
globSync
:Ahora, iteraremos cada ruta de archivo y obtendremos el contenido del archivo utilizando la biblioteca integrada fs de Node:
Genial, tenemos el código SVG de cada ícono y ahora podemos combinarlos, pero debemos reemplazar la etiqueta
svg
dentro de cada ícono con la etiquetasymbol
y eliminar los atributos SVG inútiles.
Deberíamos analizar nuestro código SVG con alguna biblioteca de análisis HTML para obtener su representación DOM. Usaré node-html-parser :
Hemos analizado el código SVG y obtenido el elemento SVG como si fuera un elemento HTML real.
Usando el mismo analizador, cree un elemento
symbol
vacío para mover elementos secundarios desvgElement
alsymbol
:Después de extraer elementos secundarios de
svgElement
, también deberíamos obtener los atributosid
yviewBox
. Comoid
, establezcamos el nombre del archivo del icono.Ahora tenemos un elemento
symbol
que se puede colocar en un objeto SVG. Entonces, simplemente defina la variablesymbols
antes de iterar los archivos, transforme elsymbolElement
en una cadena y empújelo ensymbols
:El último paso es crear el objeto SVG. Representa una cadena con
svg
en “raíz” y símbolos como hijos:const svgSprite = `<svg>${symbols.join('')}</svg>`;
Y si no estás pensando en usar complementos, de los que hablaré a continuación, debes colocar el archivo con el sprite creado en alguna carpeta estática. La mayoría de los paquetes usan una carpeta
public
:fs.writeFileSync('public/sprite.svg', svgSprite);
Y esto es todo; el script está listo para usar:
// 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);
Puedes poner este script en la raíz de tu proyecto y ejecutarlo con tsx :
npx tsx generateSvgSprite.ts
En realidad, estoy usando tsx aquí porque solía escribir código en TypeScript en todas partes y esta biblioteca le permite ejecutar scripts de nodo escritos en TypeScript. Si desea utilizar JavaScript puro, puede ejecutarlo con:
node generateSvgSprite.js
Entonces, resumamos lo que hace el script:
- Busca en la carpeta
src/icons
cualquier archivo.svg
.
- Extrae el contenido de cada icono y crea un elemento de símbolo a partir de él.
- Envuelve todos los símbolos en un solo
<svg />.
- Crea el archivo
sprite.svg
en la carpetapublic
.
Cómo cambiar los colores de los iconos
Cubramos un caso frecuente e importante: ¡los colores! Creamos un script donde el ícono va dentro de un objeto, pero este ícono puede tener diferentes colores a lo largo del proyecto.
Debemos tener en cuenta que no sólo los elementos <svg/>
pueden tener atributos de relleno o trazo, sino también path
, circle
, line
y otros. Hay una característica CSS muy útil que nos ayudará: currentcolor .
Esta palabra clave representa el valor de la propiedad de color de un elemento. Por ejemplo, si usamos el color: red
en un elemento que tiene un background: currentcolor
, entonces este elemento tendrá un fondo rojo.
Básicamente, necesitamos cambiar cada valor de atributo de trazo o relleno al currentcolor
. Espero que no lo estés viendo hecho manualmente, je. E incluso escribir algún código que reemplace o analice cadenas SVG no es muy eficiente en comparación con una herramienta muy útil , svgo .
Este es un optimizador de SVG que puede ayudar no solo con los colores sino también con la eliminación de información redundante de SVG.
Instalemos svgo :
npm i -D svgo
svgo
tiene complementos integrados y uno de ellos es convertColors
, que tiene la propiedad currentColor: true
. Si usamos esta salida SVG, reemplazará los colores con currentcolor
. Aquí está el uso de svgo
junto con 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);
Y la salida será:
<svg viewBox="0 0 24 24"><path fill="currentColor" d="m15 5 4 4"/></svg>
Agreguemos svgo
a nuestro script mágico que escribimos en la 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);
Y ejecuta el script:
npx tsx generateSvgSprite.ts
Como resultado, el objeto SVG contendrá iconos con currentColor
. Y estos íconos se pueden usar en todas partes del proyecto con el color que desee.
Complementos
Tenemos un script y podemos ejecutarlo cuando queramos, pero es un poco inconveniente que debamos usarlo manualmente. Por lo tanto, recomiendo algunos complementos que pueden ver nuestros archivos .svg
y generar sprites SVG sobre la marcha:
vite-plugin-svg-spritemap (para usuarios de vite )
Este es mi complemento que contiene básicamente este script que acabamos de crear en este artículo. El complemento tiene habilitado el reemplazo
currentColor
de forma predeterminada, por lo que puede configurar el complemento con bastante facilidad.// 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 usuarios de webpack )
Utilicé este complemento de Webpack hasta que cambié a Vite. Pero este complemento sigue siendo una buena solución si utiliza Webpack. Debes habilitar manualmente la conversión de color y se verá así:
// 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', }, }), ], }
Uso en diseño
Proporcionaré un ejemplo en React , pero puedes implementarlo donde quieras porque se trata principalmente de HTML. Entonces, como tenemos sprite.svg
en nuestra carpeta de compilación, podemos acceder al archivo sprite y crear el componente Icon
básico:
const Icon: FC<{ name: string }> = ({ name }) => ( <svg> <use href={`/sprite.svg#${name}`} /> </svg> ); const App = () => { return <Icon name="pen" />; };
El resultado final
Entonces, resumiendo todo, para evitar mucho trabajo manual con íconos, nosotros:
- Puede guardar y mantener fácilmente iconos organizados en el proyecto con nombres deseables.
- tener un script que combine todos los íconos en un solo objeto en un archivo separado que reduce el tamaño del paquete y nos permite usar estos íconos en cualquier parte del proyecto
- tener una herramienta útil que nos ayuda a mantener los íconos libres de atributos innecesarios y cambiar los colores en el lugar de uso
- tener un complemento que pueda ver nuestros archivos de íconos y generar sprites sobre la marcha como parte del proceso de construcción
- tener un componente Icon que sea una guinda
Conclusión
La eficiencia en el desarrollo no se trata sólo de ahorrar tiempo; se trata de desbloquear nuestro potencial creativo. Automatizar las tareas esenciales, como la gestión de iconos, no es sólo un atajo; es una puerta de entrada a una experiencia de codificación más fluida e impactante. Y al ahorrar tiempo en cosas tan rutinarias, podrá concentrarse en tareas más complejas y crecer como desarrollador más rápido.