ru
SVG Sprites the right way

SVG Sprites the right way

While refactoring a legacy project, it turned out that icons were added to pages in 5 different ways, from inline SVG embedding to icon fonts. Three Gulp tasks were written for this. Since it was a refactoring job, I decided to unify the way icons were embedded. At first, I wanted to use the symbol library, but this popular method of creating SVG sprites has a serious drawback — it can not work with background images. Now we'll fix that problem.

In this article, I'm not going to look at the classic way of creating sprites, because it's well known to everyone. I will talk about SVG symbols briefly and focus on the part that allows you to implement insertion of background icons using CSS.

What is the problem with classic sprites?

A classic sprite is a single file (SVG or raster) with icons on it. It is suggested that the icon be selected using the positioning of the background image.

What are the disadvantages of this method?

  • You need additional tools to create a complete image (online or as a NPM package).
  • You can't change the color of icons in CSS.
  • You can't access icons from JavaScript.

Some of these disadvantages are mitigated by the use of the SVG symbol library.

To recap briefly. Icons are added to the SVG file in the <symbol> tag.

<svg xmlns="http://www.w3.org/2000/svg">
  <symbol id="cart" viewBox="0 0 32 32">
    <path fill="currentcolor" d="M21.65…" />
  </symbol>
  <symbol id="user" viewBox="0 0 32 32">
    <path fill="currentcolor" d="M18.34…" />
  </symbol>
</svg>

In the template, the icon is represented as an inline SVG with a link to the symbol ID.

<svg viewBox="0 0 32 32" width="32" height="32" aria-hidden="true">
  <use href="sprite.svg#cart" />
</svg>

The advantages of such sprites:

  • You can change styles from CSS.
  • You can use JavaScript.
  • You can use CSS Custom Properties (aka CSS variables).
  • By assigning currentColor instead of color, you can control the color from CSS via the color property.
  • Ability to fully control the SVG, stripping out unnecessary stuff to save weight.
  • Ability to use multiple sprites for different sections of the site without having to rewrite tasks for Gulp/Webpack.

The only disadvantage of SVG symbols, as already mentioned, is the lack of support for background images. And that's where SVG Stacks technology comes to the rescue.

SVG Stacks — the answer to the problem

In the template, the sprite is rendered the same way as in SVG symbols.

<svg viewBox="0 0 32 32" width="32" height="32" aria-hidden="true">
  <use href="sprite.svg#cart" />
</svg>

In the sprite file, the changes begin. The first thing is to replace <symbol> with <svg>. Yes, that's right, SVG inside SVG, like in Nolan's "Inception". So we have an external SVG and internal wrappers.

<svg xmlns="http://www.w3.org/2000/svg">
  <defs>
    <style>
      :root svg:not(:target) {
        display: none;
      }
    </style>
  </defs>

  <svg id="cart" viewBox="0 0 32 32">
    <path fill="currentcolor" d="M21.65…" />
  </svg>

  <svg id="user" viewBox="0 0 32 32">
    <path fill="currentcolor" d="M18.34…" />
  </svg>
</svg>

We add display: none since all internal SVGs will be visible by default. Using the :target pseudo element, we only display the current icon, which we refer to by ID. Everything else is no different than using SVG characters.

Background Images

Now comes the fun part. How do I display icons as a background image?

a {
  background-image: url('sprite.svg#cart');
}

The only limitation of this approach is that we cannot explicitly change the icon fill color in this way:

a {
  background-image: url('sprite.svg#cart');
}

a:hover {
  fill: 'darkgray';
}

To do this, we need to create "instances" of the icon inside the sprite:

<svg xmlns="http://www.w3.org/2000/svg">
  <defs>
    <style>
      :root svg:not(:target) {
        display: none;
      }
    </style>

    <!-- Icon -->
    <path id="cart" d="M21.65…" />
  </defs>

  <!-- Icon instance -->
  <svg id="cart-default" viewBox="0 0 32 32">
    <use href="#cart" fill="gray" />
  </svg>

  <!-- Icon instance on hover -->
  <svg id="cart-hover" viewBox="0 0 32 32">
    <use href="#cart" fill="darkgray" />
  </svg>
</svg>

And now you can display background icons:

a {
  background-image: url('sprite.svg#cart-default');
}

a:hover {
  background-image: url('sprite.svg#cart-hover');
}

Transparent areas in Firefox

If the icon has transparent areas, in the Firefox browser the cursor may skip them on the hover. So we add a transparent square around the icon.

<svg id="cart" viewBox="0 0 32 32">
  <rect width="32" height="32" fill-opacity="0" />
  <path fill="currentcolor" d="M21.65…" />
</svg>

I think these two disadvantages more than compensate the maintenance of the several methods of creating SVG sprites.


References:

Last Updated: