Hello dev.to community!
This is my first post so I'm happy to start to share my experience (hope this will be useful for somebody) with SVG sprites here.
The problem
In my project I have many SVG icons (around 600 icons), so I decided to use SVG sprites to improve performance. A bundle size is critical in my case, so we need to implement external SVG sprites, so that they will load as a separate files.
SVG sprites implementation
To achieve that I used svg-sprite-loader library svg-sprite-loader.
First of all you should install the library as dev dependency:
npm install svg-sprite-loader --save-dev
All Webpack config code snippets provided for Webpack 4.
Initial Webpack config will look like this:
{
test: /\.svg$/,
use: [
{
loader: 'svg-sprite-loader',
options: {
extract: true,
publicPath: 'assets/sprites/',
spriteFilename: svgPath => `sprite${svgPath.substr(-4)}`
}
}
]
}
In the extract mode of the loader, it is possible to create as many sprites as you like. We have many icons in the project, so having one sprite for all the icons is not the best choice.
We can update webpack config to support multiple sprites.
For a folder structure:
src/
svg/
sprite1
sprite2
webpack config will look like this:
{
test: /\.svg$/,
include: ['sprite1', 'sprite2'].map(folder => path.resolve(__dirname, 'src', 'svg', folder)),
use: [
{
loader: 'svg-sprite-loader',
options: {
extract: true,
publicPath: 'assets/sprites/',
spriteFilename: svgPath => {
for (const folder of ['sprite1', 'sprite2']) {
if (svgPath.includes(path.resolve('src', 'svg', folder))) {
return `${folder}.svg`;
}
}
}
}
}
]
}
To use a sprite I need to import it to our component:
import pencilIcon from 'svg/sprite1/pencil-icon.svg';
pencilIcon
will have three properties:
{
id: "pencil-icon",
url: "svg/sprite1.svg#pencil-icon"
viewBox: "0 0 50 50"
}
Please note that url
property is available in the exact mode only.
To show the icon, we need to do next:
<svg viewBox={pencilIcon.viewBox} width="50" height="50">
<use href={pencilIcon.url}/>
</svg>
That works just fine!
What if we'd like to draw this icon on Canvas?
We can use Image element and set a sprite url as a source:
const image = new Image();
image.src = "svg/sprite1.svg#pencil-icon";
image.onload = () => {
this.image = image;
}
const ctx = canvas.getContext('2d');
ctx.drawImage(this.image, 100, 100, 50, 50);
It works fine in Chrome, but not in Safari and Firefox.
As a workaround here, we can load the whole sprite file by url and find an icon:
function loadSprite(url: string) {
const response = await fetch(url);
const xml = await response.text();
const parser = new DOMParser();
return parser.parseFromString(xml, 'image/svg+xml');
}
To parse the icon we can do next:
function getSvgIconFromExternalSprite(source, width, height) {
const spriteFile = loadSprite(source.url);
const symbols = Array.from(spriteFile.childNodes[0].firstChild.childNodes);
const sprite = symbols.find(node => node.id === source.id);
sprite.setAttribute('width', width);
sprite.setAttribute('height', height);
const spriteText = sprite!.outerHTML.replaceAll(/symbol/g, 'svg');
return `data:image/svg+xml;utf8,${encodeURIComponent(spriteText)}`;
}
I set width and height explicitly, because of the known Firefox issue (https://bugzilla.mozilla.org/show_bug.cgi?id=700533)
Summary
External SVG sprite can be helpful if you have a lot of icons and bundle size is critical in your case. I hope my post can help somebody to use them in your project.
Good code everyone!
Top comments (1)
How to use SVG sprite in React js? cricketbet9