SVG has a <use>
element which essentially means: go find the chunk of SVG that has this #identifier and kinda clone it and put it right here. It’s an essential ingredient to an SVG icon system. There are some things to know about this that we haven’t covered before.
As a reminder, it looks like this:
<!-- Reference IN THIS SAME DOCUMENT -->
<svg>
<use xlink:href="#icon-1"></use>
</svg>
<!-- EXTERNAL reference -->
<svg>
<use xlink:href="sprite.svg#icon-1"></use>
</svg>
That that #icon-1 identifier likely references a symbol in that file, like…
<svg xmlns="http://www.w3.org/2000/svg">
<symbol id="icon-1" viewBox="0 0 1024 1024">
<title>Kinda like alt text</title>
<path class="path-1" d="..."></path>
</symbol>
...
</svg>
The beauty of an external reference is that it leverages browser cache well.
Also that it’s simple to work with. You put the correct file path to the file (the “SVG sprite”) and reference an identifier, and it just works. You can have your server set to serve that file with all the correct headers such that the browser will hang onto it like it would any other asset you want to cache.
The problem was IE, but it’s being fixed.
Most browsers that supported inline SVG supported this, so it was almost usable, with the major exception of IE. But they have now fixed that in Microsoft Edge. Edge doesn’t have massive market share yet, but it is the future on Windows, so eventually, we’ll be able to start using this without any extra work.
Because of the lack of perfect support, there are two major workaround choices.
- Include the SVG sprite in all the HTML documents themselves. Works great. Tends to be very fast, but it bloats the page cache. And to get the best support, you need to include it at the top of the document, which means slightly delayed rendering for likely more important content. Or…
- Ajax for the sprite. Which then can leverage browser caching. It can be tricky to do this though without a bit of FONI though (Flash of No Icons).
Once we can start using external references directly, it’s not the same
This concept eluded me and is why I wanted to write this article.
I thought the external reference was the ultimate solution because it could do everything inline SVG referencing SVG in the same document could do. But unfortunately, it can’t. SVG references this way has it’s own separate DOM. It goes beyond the regular Shadow DOM boundary that all <use>
is subject to.
With this:
<svg class="icon-1">
<use xlink:href="#icon-1"></use>
</svg>
You could write CSS (in the same stylesheet you use for the rest of the site) to color it:
/* This works.
It will cascade this fill through the shapes,
as long as there are no presentational fill
attributes on the shapes themselves. */
.icon-1 {
fill: red;
}
You can still do this with an externally referenced <use>
, actually. But you can’t style individual shapes like you could before.
/* You could reach individual shapes
to style because they share the same DOM.
But this WON'T WORK with externally referenced SVG. */
.path-1 {
fill: yellow;
}
/* This won't work either way,
because it crosses a shadow DOM boundary */
.icon-1 /* ~shadow~ */ .path-1 {
fill: yellow;
}
You can’t get your hands on the internal shapes at all when you externally reference. For example, from the HTML document:
<script>
var shape = document.querySelectorAll(".path-1");
console.log(shape);
// [ ] (empty set)
</script>
Here’s a gist that kinda belabors the point.
It’s still pretty cool.
The fact that cascading in a single color still works makes it pretty useful. Most icons tend to be single-color. And you can have different single colors still.
Weird Future Stuff
Tab Atkins in his crazy future thinking ways has documented some potential future ideas called SVG params:
.foo {
background-image: url("http://example.com/image.svg" param(--color var(--primary-color)));
}
That’s a CSS example, but presumably something inline SVG could use as well.
the query string miss the dot(
class
) :pThanks! Fixed and burying this since no longer an issue.
Hey Chris, good stuff.
Just a reminder for your readers : when using
<use>
you can still color your SVG with two parameters by using fill and color css properties, and by setting the current-color value in your SVG. You said I better than me here https://css-tricks.com/cascading-svg-fill-color/I am working on a project with this technique right now and it suits my needs pretty well (all my icons are in max 3 colors so I use fill, color, and background-color on the SVG’s wrapper via transparent areas in the SVG.)
Please note that external hosts (like when you want to serve the sprite from a CDN) are not necessarily working as CORS is not implemented for such requests in most browsers: http://tympanus.net/codrops/2015/07/16/styling-svg-use-content-css/comment-page-1/#comment-466665
Yes, that’s very important!
You’d thinks CORS headers would allow this, but no such luck. I hope that’s considered a bug that will be fixed, but I’m not sure (maybe it’s dangerous to a degree that will never be allowed?)
So, you can reference as an external URL, but only same-domain.
Well, it’s a reported bug on Chromium (not sure for the others) but there hasn’t been much activity on it: https://code.google.com/p/chromium/issues/detail?id=470601. Still hope it will be fixed soon.
I don’t see any risk in allowing it when a proper CORS and CSP is in place.
I found a solution to have an external sprite sheet, using
use
andsymbol
, and being able to style each element inside the symbol with normal CSS, as if it were inline. The best part is that it works across all the browsers and you’re probably already using the right library.What svg4everybody does is replace the
use
tags with the actual symbol’s content—but only in IE and old Safari. By settingpolyfill:true
you force this behavior on all browsers.I suggested it on https://github.com/jonathantneal/svg4everybody/issues/82 and I’m successfully using it on http://toyotahalloffame.com
So presumably, when we want to stop using svgforeverybody because Old IE no longer needs to be supported, somebody could extract this functionality into a smaller polyfill which we could all use? It’s probably beyond my ability to scan the svg4everybody code and find out where the magic is, but I’m sure there’s somebody clever out there that could make a small polyfill which just helps us out with this?
Even better: then each instance is completely separate, so you could style even multiple shapes inside differently. It’s just at the cost of 1) an extra (small) library 2) at least one XHR request (does it do one per icon or one total?) 3) dom weight
Jon: I don’t think there’s anything to extract; svg4everybody does exactly that thing, but by default it only does it as a “polyfill”. The only improvement that could be done would be removing the UA check.
That’s not the reason. The reason is that CSS rules do not apply across file boundaries. You can’t style it because it is in a separate file.
That’s exactly what I mean. It’s a different DOM. But you can still SEE that DOM, like if you web inspect it. So it LOOKS just like a regular
<use>
, with the Shadow DOM and all, but it plays by different rules.Nope, the browser fetches the external file, and duplicates the used
<symbol>
s inside the document as a shadow DOM. So that this in your declared HTML:… gets turned into this in the document:
… and you can’t select through the shadow root.
Well, sometimes you can:
In Firefox, the
<use>
implementation is a bit old, predates the Shadow DOM specs quite a bit, and lets you select the “copied” elements. Which is a bug in itself, and it can also create bugs in your own code because if you dosvg { fill: red; }
it will apply to the inner SVG element as well, and when you later try to change the color of a specific icon with e.g..cool-icon { fill: blue; }
the icon will stay red.In Chrome, you can use the shadow-piercing combinator like this:
svg /deep/ path { fill: green; }
, but this experimental feature is deprecated and is supposed to get removed in the future (following a decision at the latest Web Components face-to-face meeting).Great points Chris,
While I love SVG (sprites) there are a lot of issues to take into account.
Another thing: since people copy-paste our code examples it would be great if we could advocate the most robust and accessible markup IMO.
I am no a11y expert, but in my understanding we could/should take some extra steps to make out SVG sprite icons more accessible.
Below my current thinking, I would love an a11y expert’s advice on this!
First: make a distinction between ‘presentational’ and ‘content’ icons. Just as we would with
img
and theiralt
attributes.For all SVG Icon sprites (in
sprite.svg
):Add a
title
element child to the sprite’ssymbol
Add a
desc
element child to the sprite’ssymbol
Now, for
use
ing those symbols as purely presentational icons:Use
role="presentation"
on the SVGThat’s it. But for SVG icons as ‘content’ we should do more:
Add
role="img"
Add a
title
attributeAdd a
title
element with uniqueid
Add
aria-labelledby="title-id"
This ensures the best accessibility AFAIK.
Again: I am no a11y expert, so please correct me when I’m mistaken.
Source information:
http://codepen.io/NathanPJF/full/GJObGm
https://www.paciellogroup.com/blog/2013/12/using-aria-enhance-svg-accessibility/
https://github.com/jonathantneal/svg4everybody#readability-and-accessibility
Interesting, I downloaded your gist, couldn’t see anything in the preview box.
Chrome: Shows green spade, black pencil
Firefox: Shows green spade, green pencil
IE: Shows just the green spade
Did you run it with a real web server? That may be necessary.
May i can make this works? =)
Sry, demo =)
http://nezed.github.io/SVG-External-Reference-as-bg/
I’ve dealt with this problem a few weeks ago. I’ve suggested to one of my company’s clients an icon refactor that would replace old png sprites per svg since we need a lot of customization.
External reference was on my mind since Chris’ first article about it, but when i realized that there were some multi part icons in the system that couldn’t be individually styled with this technique i went panic.
Fortunately i’ve found an article by Osvaldas Valutis that uses localStorage to cache the sprite and inject it inline. If localStorage is full, it loads and injects the sprite anyway. That’s how i’ve dealt with it, although this technique is not perfect and it slows down the rendering of icons by a fraction of time.
I will definitely try Federico Brigante technique that uses svg4everybody little trick and see which one performs better!
Keep rockin’ Chris!
A few extra points:
As Anselm Hannemann notes above, there is not currently any way to enable CORS content in SVG. It’s a planned addition to the spec, but not reliably supported in browsers.
Although the SVG 1.1 spec would suggest that styles set in CSS in the external file should still apply to the cloned content, in practice that was very problematic. SVG 2, leaving it up to browsers whether or not they process any stylesheet blocks in the external document, and therefore warns against authors relying on any
<style>
rules in the external file. Presentation attributes and inlinestyle
attributes should work as normal.There are also some issues with determining the values of percentages for elements in an external file, because that file doesn’t have a viewport to create the reference 100% width/height. However, that’s never an issue when using
<symbol>
, because the symbol creates its own reference frame for the percentages.Duplicated content, whether from the same or external file, will be stylable with inherited CSS custom properties (aka CSS variables). See blog post with demos that already work in Firefox. The SVG parameters you would specify in a URL function would map to CSS custom properties inherited by the root element. For
<use>
elements, you’d just set them on the<use>
with CSS, and the duplicated content would inherit them.If one of the symbols links to the gradient from within the same file, and this file is used as an external reference, then the gradient won’t be shown in Chrome, the same applies to any other references (e.g. filters)
Got this issue in my project: https://github.com/w0rm/gulp-svgstore/issues/56