CSS-Tricks
How SVG Fragment Identifiers Work
I've talked a good bit about SVG's <use>
around here and using it to build an icon system. The beauty of <use>
is that you can reference just a part of some SVG defined elsewhere and draw just that part somewhere else. That ability allows you to build a whole system out of it, solving the "many images in one request, because that's super efficient" problem that we've solved in the past with CSS sprites and icon fonts.
But <use>
means inline SVG. It doesn't help you when you want to use a part of a larger SVG in SVG-as-<img>
or SVG-as-background-image
. That's where fragment identifiers come in.
Getting the SVG Ready For It
One way to do this is to lay out the SVG (sprite, I guess, let's just call it a sprite) like a graphical "CSS" sprite.
We're specifically doing it this way, because we ultimately are going to shift some viewBox
numbers around to only reveal a part of this image, just like we used to do with CSS sprites.
In this little mini demo, we're using three icons that are 32x32 each. So the document is 32x96. We could think of the viewBox shenanigans this way:
Whole document viewBox | 0 0 32 96 |
Just show top icon viewBox | 0 0 32 32 |
Just show middle icon viewBox | 0 32 32 32 |
Just show bottom icon viewBox | 0 64 32 32 |
The viewBox
attribute goes: left, top, width, height. Note that second attribute, the top, notching up by 32 each time. We're just showing a different part of the whole document.
Adding those special viewBox's into the SVG itself
You can drop those special, specific viewBox
values into the SVG in a <view>
element, that is specifically for this:
<view id="icon-clock-view" viewBox="0 0 32 32" />
<view id="icon-heart-view" viewBox="0 32 32 32" />
<view id="icon-arrow-right-view" viewBox="0 64 32 32" />
Now we'll be able to reference and use those values from elsewhere.
The <view>
elements can be stand-alone like this, or they can actually wrap other elements, in which case that viewBox
takes hold when the ID matches, so
<!-- this viewBox takes over if current fragment identifer is #match-me -->
<view id="match-me" viewBox="0 64 32 32">
<rect ...>
</view>
Demo of this kind of thing in the spec.
Syntax for HTML
To apply those special viewBox
values to SVG-as-<img>
, you could do it like this:
<!-- top icon -->
<img src="sprite.svg#svgView(viewBox(0, 0, 32, 32))" alt="">
Or, if you set up <view>
elements already, you can just reference them by name:
<!-- middle icon -->
<img src="sprite.svg#icon-heart-view" alt="">
Syntax for CSS
You can declare a special viewBox
right in the image path in the CSS:
.icon-clock {
background: url("sprite.svg#svgView(viewBox(0, 0, 32, 32))") no-repeat;
}
Or reference a <view>
element if you have those set up:
.icon-clock {
background: url(sprite.svg#icon-clock-view) no-repeat;
}
Although... if you're using SVG through CSS this way and went through the trouble to set up the SVG all spaced out like this, you might wanna just use the CSS sprite technique and shift the background around:
.icon-heart {
background: url("sprite.svg") no-repeat;
background-size: 32px 96px;
background-position: 0 -32px;
}
I just wanna stack the icons on top of each other.
If the icons all have the same viewBox
and you essentially just want to hide/show them as needed, this can get a little easier.
Design them all in the same space (or use a build tool that does it or whatever). Here, I'm putting each icon in it's own group with a unique ID.
The trick to making the hide/show work then is embedding some CSS that 1) hides everything 2) reveals the one with a matching fragment identifier. CSS is up for the job, because of the :target
selector.
All together in the SVG:
<defs>
<style>
g {
display: none;
}
g:target {
display: inline;
}
</style>
</defs>
<g id="icon-clock">
<path d="M20.6,23.3L14,16.7V7.9h4v7.2l5.4,5.4L20.6,23.3z M16-0.1c-8.8,0-16,7.2-16,16s7.2,16,16,16s16-7.2,16-16S24.8-0.1,16-0.1z
M16,27.9c-6.6,0-12-5.4-12-12s5.4-12,12-12s12,5.4,12,12S22.6,27.9,16,27.9z"/>
</g>
<g id="icon-heart">
<path d="M32,11.2c0,2.7-1.2,5.1-3,6.8l0,0L19,28c-1,1-2,2-3,2s-2-1-3-2L3,18c-1.9-1.7-3-4.1-3-6.8C0,6.1,4.1,2,9.2,2
c2.7,0,5.1,1.2,6.8,3c1.7-1.9,4.1-3,6.8-3C27.9,1.9,32,6.1,32,11.2z"/>
</g>
<g id="icon-arrow-right">
<path d="M32,15.9l-16-16v10H0v12h16v10L32,15.9z"/>
</g>
Browser Support
Can I Use tracks support for fragment identifier. Unfortunately it's not quite as simple as works-or-not, because depending on the browser it might work in HTML and not CSS or have quirks.
Here's my test page:
See the Pen SVG Fragment Identifiers in HTML and CSS by Chris Coyier (@chriscoyier) on CodePen.
I haven't made super detailed browser support notes for this, but here's the highlights:
- Firefox does everything right.
- IE 11 does everything right. IE 9 & 10 are a little weird with
background-position
(squishing) but otherwise works with all. - Current versions of Chrome/Safari/Opera (38/8/25) handle all the HTML
<img>
techniques well, but none of the CSS techniques, including the background-position one. Pre-Blink Opera was the same, interestingly enough. - The only thing iOS 8.1 can handle is
<img>
referencing a<view>
. - The only thing Android 4.4 gets right is background-position (the one that's not really using fragment identifiers at all). Android 5 matches current Chrome/Safari/Opera as above.
Linkage!
- Spec
- SVG Stacks by Simuari, which points to a demo by Erik Maelström and a demo by Bear Travis.
- Demo by Jorge Aznar
- Better SVG Sprites With Fragment Identifiers by Peter Gasston
- SVG Sprite Sheets by Bear Travis
How SVG Fragment Identifiers Work is a post from CSS-Tricks