Why the <use> element in SVG doesn't work? - html

I have the following simple example. It is stored in image.svg:
<svg>
<defs>
<g id="shape">
<circle cx="100" cy="100" r="100" />
</g>
</defs>
</svg>
However, putting this code in a HTML file doesn't load anything. Why is that?
<svg>
<use xlink:href="#shape" x="10" y="10" />
</svg>
What am I doing wrong? I can't seem to make it work.

If you are using elements from another document, you have to specify the document!
<use xlink:href="#shape" x="10" y="10" />
This means "use the #shape element from the current document".
To import from another document, you need to put the reference to the SVG file in the xlink:href attribute:
<use xlink:href="image.svg#shape" x="10" y="10" />
Obviously you need to check the path is correct here. Note that this is not supported in any version of Internet Explorer, though polyfills are available.

For external svg files you need the namespace ... and I have added a fill to render the circle otherwise it will be transparent:
<svg xmlns="http://www.w3.org/2000/svg" >
<symbol id="shape" width="200" height="200" viewbox="0 0 200 200">
<circle cx="100" cy="100" r="100" fill="currentColor" />
</symbol>
<text y="20">Symbol above will not render unless referenced by use element</text>
</svg>
Then when you reference it you need to use the correct namespace for xlink:
svg.defs-only {
display:block; position: absolute;
height:0; width:0; margin: 0; padding: 0;
border: none; overflow: hidden;
}
svg {
color: orange;
stroke: red;
}
.purple {
color: purple;
stroke: black;
}
<svg class="defs-only" xmlns="http://www.w3.org/2000/svg" >
<symbol id="shape" width="50" height="50" viewbox="0 0 50 50">
<circle cx="25" cy="25" r="20" fill="currentColor" stroke="inherit" />
</symbol>
</svg>
<svg xmlns:xlink="http://www.w3.org/1999/xlink">
<use xlink:href="#shape" x="10" y="10" />
<use xlink:href="#shape" x="80" y="10" class="purple" />
</svg>
If you are referencing an external file, you need to put the filename before the # e.g. image.svg#shape making sure you get the path correct of course.
Note, not all browsers support fragment identifiers - notably IE and Edge - you need to use a javascript polyfill like svg4everybody for those browsers.
Workaround - use svg inline only

You need to have the use-tag inside the SVG with the shape you want to use:
<svg>
<defs>
<g id="shape">
<circle cx="100" cy="100" r="100" />
</g>
</defs>
<use xlink:href="#shape" x="10" y="10" />
</svg>

SVG 2 (when implemented in browsers) will allow to reference another SVG file without any fragment identifier:
New in SVG 2: An href without a fragment allows an entire SVG document to be referenced without having to ensure that it has an ID on its root element.
Before (SVG 1.1):
<!-- my-vector.svg -->
<svg id="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<circle r="10" cx="12" cy="12" />
</svg>
<use href="my-vector.svg#icon"></use>
After (there will be no need to define id="..." on the svg):
<!-- my-vector.svg -->
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<circle r="10" cx="12" cy="12" />
</svg>
<use href="my-vector.svg"></use>
SVG 2 seems to be in the process of development in major browsers (see this Chrome feature and specifically this Chromium issue: Issue 366545: [SVG2] Allow to reference entire files).

Related

Is it legal to put <svg> resources after </body>?

Can I put svg resources used in the website behind the end of body, in order to keep
them outside of what will be rendered?
In short: Is it legal to do the following?
<!DOCTYPE html>
<html>
<head>...</head>
<body>
<svg xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 315.424 315.424" >
<use href="#arrow" fill="rgb(0,44,89)" />
</svg>
</body>
<svg>
<g id="arrow">
<path d="M311.929,222.266l-96.119-67.342c-1.413-0.99-2.783-1.513-4.307-1.513c-3.307,0-6.471,2.512-6.471,7.313v41.05H19.886
c-4.962,0-8.854,4.132-8.854,9.094v35.563c0,4.962,3.892,9.343,8.854,9.343h185.146v40.81c0,4.801,3.167,7.19,6.474,7.19
c0.001,0-0.089,0-0.089,0c1.524,0,3.032-0.461,4.445-1.451l96.09-67.306c2.214-1.55,3.473-3.864,3.473-6.375
S314.142,223.815,311.929,222.266z" />
</g>
</svg>
</html>
As mentioned before in the comments:
It's not valid and many browser won't display your elements.
So inserting your svg assets at the top or bottom of your <body> is the preferrable method.
Caution: Hiding your asset svg via display:none will break some referenced definitions like:
filters
gradients
masks and clip-paths
It works flawlessly for shape elements (like icons)
Example
function displayNone() {
document.querySelector('#svgAssets').style.display = 'none';
}
svg {
border: 1px dotted #ccc;
height: 10em;
display: inline-block;
}
<p><button onclick="displayNone()">Set display:none</button></p>
<svg viewBox="0 0 315.424 315.424">
<use href="#arrow" fill="red" />
</svg>
<svg viewBox="0 0 100 100">
<use href="#circle" fill="green" />
</svg>
<svg viewBox="0 0 200 100">
<use href="#ellipse" fill="url(#gradient)" />
</svg>
<svg id="svgAssets" style="visibility:visible; width:0; height:0" aria-hidden="true">
<defs>
<linearGradient id="gradient">
<stop stop-color="red" offset="0%"/>
<stop stop-color="orange" offset="100%"/>
</linearGradient>
</defs>
<symbol id="arrow" viewBox="0 0 315.424 315.424">
<path d="M311.929,222.266l-96.119-67.342c-1.413-0.99-2.783-1.513-4.307-1.513c-3.307,0-6.471,2.512-6.471,7.313v41.05H19.886 c-4.962,0-8.854,4.132-8.854,9.094v35.563c0,4.962,3.892,9.343,8.854,9.343h185.146v40.81c0,4.801,3.167,7.19,6.474,7.19 c0.001,0-0.089,0-0.089,0c1.524,0,3.032-0.461,4.445-1.451l96.09-67.306c2.214-1.55,3.473-3.864,3.473-6.375 S314.142,223.815,311.929,222.266z" />
</symbol>
<symbol id="circle" viewBox="0 0 100 100">
<circle cx="50%" cy="50%" r=50% />
</symbol>
<symbol id="ellipse" viewBox="0 0 200 100">
<ellipse cx="100" cy="50" rx="100" ry="50" />
</symbol>
</svg>
In the above example I'm using <symbol> elements which could be used as an alternative to <defs>. They also support different viewBox properties for each icon.
If you just need to place icons via <use> you could also use external file references like so:
<svg viewBox="0 0 315.424 315.424">
<use href="svgIcons.svg#arrow" fill="red" />
</svg>
However, your svg files need to be same domain (or send with appropriate CORS headers)
If the purpose is not to have the original #arrow rendered in the document, you might include it inside the svg in the body, wrapped around defs.
Demo in the snipped below.
<svg xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 315.424 315.424">
<defs>
<g id="arrow">
<path d="M311.929,222.266l-96.119-67.342c-1.413-0.99-2.783-1.513-4.307-1.513c-3.307,0-6.471,2.512-6.471,7.313v41.05H19.886
c-4.962,0-8.854,4.132-8.854,9.094v35.563c0,4.962,3.892,9.343,8.854,9.343h185.146v40.81c0,4.801,3.167,7.19,6.474,7.19
c0.001,0-0.089,0-0.089,0c1.524,0,3.032-0.461,4.445-1.451l96.09-67.306c2.214-1.55,3.473-3.864,3.473-6.375
S314.142,223.815,311.929,222.266z" />
</g>
</defs>
<use href="#arrow" fill="rgb(0,44,89)" />
</svg>

How can I bundle many SVG images inside just one?

It's not bad if I load an HTML page
img, svg {
background-color: #eee;
margin: 20px;
}
<div>
<img src="circle.svg"/>
<img src="square.svg"/>
</div>
with just a pair of SVG images
circle.svg
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 400 300" width="400" height="300">
<circle cx="200" cy="150" r="100"
stroke="red" fill="blue" stroke-width="10" />
</svg>
square.svg
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 400 300" width="400" height="300">
<rect x="100" y="50" width="200" height="200"
stroke="green" fill="gray" stroke-width="10" />
</svg>
but with over a hundred SVG images, the server gets excessively hit by requests.
One solution is to serve the static files from a dedicated server, but this only dodges the problem. The number of requests remains high.
How can I bundle many SVG images inside just one?
You could use a SVG sprite generator to create one big file with all images aligned in it.
SVG sprite generator will also generate a CSS file in which each individual SVG will be represented with a specific class.
In you HTML you just have to call each image by its class name.
A basic sprite.css could be :
.svg {
background-image: url(sprite.svg) no-repeat;
}
.circle {
background-position: top left;
height:300px;
width: 400px;
}
.square{
background-position: top right;
height:200px;
width: 200px;
}
And then in your html file you could just call:
<div>
<div class="circle"></div>
<div class="square"></div>
</div>
It sounds like you need an SVG sprite. I use this trick all the time. It's great. Just make your svg blocks symbol elements and nest them inside an svg like this:
<svg id="svg-sprite" xmlns="http://www.w3.org/2000/svg">
<symbol id="svg-circle" viewBox="0 0 400 300" width="400" height="300">
<circle cx="200" cy="150" r="100" stroke="red" fill="blue" stroke-width="10" />
</symbol>
<symbol id="svg-square" viewBox="0 0 400 300" width="400" height="300">
<rect x="100" y="50" width="200" height="200" stroke="green" fill="gray" stroke-width="10" />
</symbol>
</svg>
Note that you don't want the xlmns attribute on the individual symbol elements, just the root svg. And the root svg doesn't need a viewBox attribute, since that is encoded in the child symbol elements.
Then you call the symbols elsewhere in the HTML like this via the <use> tag:
<svg>
<use xlink:href="#svg-circle"></use>
</svg>
Lastly, you need to hide the sprite in CSS:
#svg-sprite {
display: none;
}
Here's a Fiddle to demonstrate. Good luck!
The following is a combination of gael's and maqam7's answers, with a bug fix and some details.
First, we combine the two SVGs into one. (We write our own script, use an editor's macros, use one of the web sites that do it, or do it by hand.)
sprite.svg
<svg id="mysprite" xmlns="http://www.w3.org/2000/svg">
<symbol id="circle"
viewBox="0 0 400 300"
width="400" height="300">
<circle cx="200" cy="150" r="100"
stroke="red" fill="blue" stroke-width="10" />
</symbol>
<symbol id="square"
viewBox="0 0 400 300"
width="400" height="300">
<rect x="100" y="50" width="200" height="200"
stroke="green" fill="gray" stroke-width="10" />
</symbol>
</svg>
When we want a circle or a square, we use the xlink:href attribute (deprecated but continue to use it), which will invoke a sub-sprite.
<div class="container">
<svg>
<use xlink:href="sprite.svg#circle"></use>
</svg>
<svg>
<use xlink:href="sprite.svg#square"></use>
</svg>
</div>
There is no need to include the sprite in the body
<img src="sprite.svg"/>
as the sprite is referenced within each svg element.
Hence there is no need to hide the global sprite.
#svg-sprite {
display: none;
}
Only the sub-parts appear.
One caveat: Chrome loads an img and svg directly, but will refuse to load use/xlink:href unless you run a local server.
Remaining issue(s)
I'm not sure this is optimal. It may be that two requests continue to be sent. It's just that the cache will catch the second as identical. No harm is done. Still, loading once via a hidden svg may be a better approach, if someone can fill in the details.

Why is this an empty space appearing inside of SVG element?

I have the fiddle with this problem. If I set viewBox property on the symbol element, icon displayed correctly. But if I set viewBox with same value on svg element with use inside, around use element appears weird empty space, inside of SVG-container.
Viewport of first variant is the same with viewport of second variant - 35x35px size at 2.5x3.5px coords.
What's the reason of this behavior? Bug or my own mistake?
EDIT: Add picture with correct and incorrect areas.
#svg-icons {
display: none;
}
.icon {
display: inline-block;
border: 1px solid red;
}
.icon + .icon {
margin-left: 20px;
}
<svg version="1.1" id="svg-icons" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<symbol id="icon-1" viewBox="2.5 3.5 35 35">
<rect x="2.5" y="3.5" stroke="#000000" stroke-miterlimit="10" width="35" height="35" />
<circle fill="#FFFFFF" stroke="#000000" stroke-miterlimit="10" cx="20" cy="21" r="14" />
</symbol>
<symbol id="icon-2">
<rect x="2.5" y="3.5" stroke="#000000" stroke-miterlimit="10" width="35" height="35" />
<circle fill="#FFFFFF" stroke="#000000" stroke-miterlimit="10" cx="20" cy="21" r="14" />
</symbol>
</svg>
<svg class="icon icon-1" width="100" height="100">
<use xlink:href="#icon-1"></use>
</svg>
<svg class="icon icon-2" viewBox="2.5 3.5 35 35" width="100">
<use xlink:href="#icon-2"></use>
</svg>
This probrem will be solved by thinking about how the use element is treated on drawing.
According to SVG 1.1 2nd edition, use element refers symbol element is treated as svg element on drawing. So the result by that svg structure is same as this.
<svg class="icon icon-2" viewBox="2.5 3.5 35 35" width="100">
<g>
<svg width="100%" height="100%">
<rect x="2.5" y="3.5" stroke="#000000" stroke-miterlimit="10" width="35" height="35" />
<circle fill="#FFFFFF" stroke="#000000" stroke-miterlimit="10" cx="20" cy="21" r="14" />
</svg>
</g>
</svg>
Inner svg element has range of 100% width and height positioned at (0,0), and outer svg element has viewBox offset to (2.5, 3.5).
Thus shapes are cutoff by displacement of 2 svg element's rendering ranges.

Multiple perspectives into a single SVG

Is it possible to have a multiple different views into single SVG, or even just simulate that sort of effect with some clever use of groups? I wish to show different parts of a potentially very large SVG, but I'd rather avoid rendering it multiple times. Is there some sort of simple way of doing this?
for standalone SVGs there is the <view/> element which you can use to show only portions of your graphics. try this in a standalone file.
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100" width="100" height="100">
<view id="circleView" viewBox="0 0 100 100"/>
<view id="rectView" viewBox="100 0 100 100"/>
<a xlink:href="#rectView">
<circle cx="50" cy="50" r="45" fill="blue"/>
</a>
<a xlink:href="#rectView">
<rect x="105" y="5" width="90" height="90" fill="royalblue" stroke="#53c"></rect>
</a>
</svg>
just click on the cirlce to see the rect and on the rect to see the circle.
this also works if you reference your svg via <img>
<img src="your.svg#circleView"/>
<img src="your.svg#rectView"/>
i found this to be not working for inlined SVG. Here you can use a similar aproach. You can just change the viewBox of your SVG. In contrast to the above, viewBoxes can even be animated!
<svg viewBox="0 0 460 360" width="200" height="200">
<polygon id="triangle" points="100,10,450,350,10,350" fill="#52c" />
<circle id="circle" cx="50" cy="50" r="45" fill="#c52" />
<rect id="rect" x="255" y="155" width="200" height="200" fill="#5c2" />
<animate attributeName="viewBox" to="250 150 210 210" dur="0.5s" begin="circle.click" fill="freeze" />
<animate attributeName="viewBox" to="0 0 100 100" dur="0.5s" begin="triangle.click" fill="freeze" />
<animate attributeName="viewBox" to="0 0 460 360" dur="0.5s" begin="rect.click" fill="freeze" />
</svg>
<br/>click on any of he shapes!
of course you can also just set the viewBox by script...
if you want to reference different parts of your SVG, you might use the <use> - Element as suggested in the other answers.
It's pretty simple to do. You just use the <use> element as Robert suggests. Here is a working example.
svg {
border: solid 1px black;
}
svg#original {
width: 450px;
}
<svg viewBox="0 0 450 300" id="original">
<circle cx="225" cy="150" r="150" fill="orange"/>
<circle cx="175" cy="110" r="25" fill="black"/>
<circle cx="275" cy="110" r="25" fill="black"/>
<circle cx="225" cy="70" r="150" fill="none" stroke="black" stroke-width="20" stroke-dasharray="0 145 180 1000"/>
</svg>
<br/>
<!-- part of the original at the same scale -->
<svg width="150" height="150">
<use xlink:href="#original" x="-50" y="0" width="450" height="300"/>
</svg>
<!-- part of the original at 0.5x scale -->
<svg width="150" height="150">
<use xlink:href="#original" x="0" y="0" width="450" height="300" transform="scale(0.5)"/>
</svg>
<!-- part of the original at 3x scale (and using a different method to achieve the scaling) -->
<svg width="150" height="150">
<use xlink:href="#original" x="-450" y="-255" width="1350" height="900"/>
</svg>

Change properties of a imported SVG using css

I am quite new in website building and currently deal with the Scalable Vector Graphics (SVG). I have read some online materials that <object> is suggested to add an .svg file on website, e.g.
<object type="image/svg+xml" data="/img/svg/home78.svg"></object>
It is nice that the svg appears nicely, but I would like to change its properties like changing the original colour from black to blue. Is there any way to do by using css? Or are there any alternatives?
Thanks.
Using Inline Style Sheets
It is possible to define the styles for your shapes in an inline style sheet, and then have all these styles automatically applied to your shapes. Here is an example:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<style type="text/css">
<![CDATA[
.mycircle {
stroke: #006600;
fill: #00cc00;
}
.otherclass {
fill: blue;
}
]]>
</style>
<circle class="mycircle" cx="40" cy="40" r="24" />
<circle cx="120" cy="40" r="24" fill="red" />
<circle class="otherclass" cx="200" cy="40" r="24" />
</svg>
In addition, you can use #import url("/path/to/your.css"); to maintain separate css like this
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<style type="text/css">
#import url("/path/to/your.css");
</style>
<circle class="mycircle" cx="40" cy="40" r="24" />
<circle cx="120" cy="40" r="24" fill="red" />
<circle class="otherclass" cx="200" cy="40" r="24" />
</svg>
aside note: I cant use a stacksnippet here, due is unable to import additional resources.
Another Alternative:
You can use javascript to alter the <svg> programmatically like this:
document.getElementById('circle1')
.setAttribute('fill','red');
document.getElementById('circle2')
.setAttribute('fill','yellow');
document.getElementById('circle3')
.setAttribute('fill','green');
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<circle id="circle1" cx="40" cy="40" r="20" />
<circle id="circle2" cx="40" cy="80" r="20" />
<circle id="circle3" cx="40" cy="120" r="20" />
</svg>
Related Answer: https://stackoverflow.com/a/27462277/2573335