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 .
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.
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/>
:
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 etiqueta symbol
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 de svgElement
al symbol
:
Después de extraer elementos secundarios de svgElement
, también deberíamos obtener los atributos id
y viewBox
. Como id
, establezcamos el nombre del archivo del icono.
Ahora tenemos un elemento symbol
que se puede colocar en un objeto SVG. Entonces, simplemente defina la variable symbols
antes de iterar los archivos, transforme el symbolElement
en una cadena y empújelo en symbols
:
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:
src/icons
cualquier archivo .svg
.
<svg />.
sprite.svg
en la carpeta public
.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.
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', }, }), ], }
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" />; };
Entonces, resumiendo todo, para evitar mucho trabajo manual con íconos, nosotros:
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.