DEV Community

Konstantin Karagashev
Konstantin Karagashev

Posted on

How to implement and draw external SVG sprites

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)}`
         }
       }
     ]
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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`;
              }
            }
          }
        }
      }
    ]
}
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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');
}
Enter fullscreen mode Exit fullscreen mode

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)}`;
}
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
lucillejodion profile image
LucilleJodion

How to use SVG sprite in React js? cricketbet9