DEV Community

Cover image for External SVGs that you can style
Ivan Lyagushkin
Ivan Lyagushkin

Posted on

External SVGs that you can style

You can inline SVGs, use a symbol sprite, make it an <img>, an <object> even an <iframe> tag, you can use data URI, css backgrounds, filters and many more techniques just to show it to a user. But if you want to utilize all this fancy power of SVGs, you have to inline. Had to. Until now.

Fancy power of SVGs

What i mean here is a possibility to manipulate SVG's styles from css. It is a must-have feature, especially for icons. You want to change the icon's fill on hover and you don't want to use a separate image for this.

To achieve this you either inline SVGs directly to your HTML, or you can use a separate file, a symbol sprite, that is also needs to be inserted to the DOM. No other technique allows you to do this. So let's look at inlining and sprites closer.

Inlining

Inlining is the simplest and the most powerful way of using SVGs. You just place an SVG right to the HTML markup and it works.

<span>
  <svg viewBox="0 0 16 16" width="16" height="16">
    <!-- svg content -->
  </svg>
</span>
Enter fullscreen mode Exit fullscreen mode

You can then style it with css, change almost any property, even position and animate specific parts of it. But there is an obvious cons of doing so:

  1. If the SVG is big, it takes additional time for the whole page to load.
  2. When using in React with (great) tools like SVGR, it increases the javascript bundle size.
  3. You can't actually control the loading process, like with basic images for example.

Sprite

Another technique that allows to manipulate SVG from css is a sprite of <symbols>. Content of SVGs is stored in the separate hidden file, and with <use> tag you can reference the right symbol from where you want your image to appear.

<!-- hidden sprite on the top of the page -->
<svg style="display:none">
  <symbol id="icon" viewBox="0 0 16 16" width="16" height="16">
    <!-- svg content -->
  </symbol>
</svg>

<!-- usage -->
<span>
  <svg><use href="#icon" /></svg>
</span>
Enter fullscreen mode Exit fullscreen mode

The sprite may also be stored in the external file, if you don't care about IE11:

<svg>
  <use href="https://cdn.net/sprite.svg#example"/>
</svg>
Enter fullscreen mode Exit fullscreen mode

Unfortunately you can't set reference to separate SVG-file in <use>, you only need a sprite, so this won't work:

<svg>
  <use href="https://cdn.net/icon.svg"/>
</svg>
Enter fullscreen mode Exit fullscreen mode

Fancy css-manipulation here works, but is limited. You don't have an access through the Shadow boundary, that <use> represents.

The good news is that cascading is working, so you can change the fill of your SVGs and use currentColor inside any property. It's just enough for the most of icons-related cases:

<!-- sprite -->
<svg style="display:none">
  <symbol id="icon">
    <path fill="currentColor">
  </symbol>
</svg>

<!-- icon with class name -->
<span>
  <svg class="icon"><use href="#icon" /></svg>
</span>

<!-- css -->
<style>
  .icon {
    color: rebeccapurple;
  }
</style>
Enter fullscreen mode Exit fullscreen mode

The bad thing about sprites is that they are not extendable. You'll end up generating huge files that you have to load on every page and you don't need 99% of it's content.

Compromise

We obviously need a compromise here, a way to load only necessary SVGs, don't bother our javascript with rendering it and keep the possibility of css-styling.

And there is such, we can fetch! We need a tool that:

  1. Fetches SVGs in browser. And caches them.
  2. Puts them into sprite.
  3. Provides an API to pass reference to this SVG to <use> tag.

The first one to think about this was Chris Coyier in 2015. But no one still have written a tool for this. So we did.

Handy SVG

It is called Handy SVG and it lives on github and publishes to npm. So you can:

npm i handy-svg
Enter fullscreen mode Exit fullscreen mode

And then in your React code:


import {HandySvg} from 'handy-svg';
import iconSrc from './icon.svg';

export const Icon = () => (
    <HandySvg
        src={iconSrc}
        width="32"
        height="32"
    />
);
Enter fullscreen mode Exit fullscreen mode

Or if you don't use React:

import {injector} from 'handy-svg/lib/injector';

const src = "https://cdn-server.net/icon.svg";

// Fetches svg content and puts it to sprite
injector.load(src);

// Gets the id of your svg in sprite
const id = injector.getId(src);

// Then you can use it at your will
const svg = `<svg><use href="#${id}" /></svg>`;
Enter fullscreen mode Exit fullscreen mode

Of course it's a compromise, there are some limits, a bit of a FONI (Flash of No Icons) for example. But still, from all the possibilities that we have, this is the most handy way to use SVG on the web right now.


The PR's are very welcome and I will be happy to hear any feedback.

Top comments (0)