Don't render SVG element with definitions only - html

I'd like to have one <svg> tag in my page that includes all my SVG definitions that I'll be using on other SVG elements.
Something like this:
<html>
<head>
<svg>
<defs>
<filter id="shadow1">
<feDropShadow dx="3" dy="7" stdDeviation="2"/>
</filter>
</defs>
</svg>
</head>
<body style="display: flex">
<svg viewBox="0 0 100 100" width="100">
<path d="M0,0 L50,50" stroke="red" filter="url(#shadow1)" />
</svg>
</body>
</html>
This works, but the problem is that the browser renders the <svg> tag that's in the <head> as if it was inside the body.
If I set width=0 and height=0 on the <svg> tag, it disappears but it's still a child element of the <body>.
Is it possible to have an <svg> tag with definitions only but outside the page's <body>?
Or a least some way of telling the browser that the <svg> element is purely declaratory and not meant to be part of the visible area of the document.
P.S.: I don't want to use and external .svg file because the definitions inside are dynamic and depend on the page being viewed.

No, SVGElement (<svg>) is not part of the meta-data contents, only content-type allowed in <head>. It must be a descendant of the <body>.
But you can place it absolutely with CSS, set its z-index to -1, set its size attributes to 0 and it would not be rendered.
svg.defs {
position: absolute;
z-index: -1;
}
<svg width="0" height="0" class="defs">
<defs>
<filter id="shadow1">
<feDropShadow dx="3" dy="7" stdDeviation="2"/>
</filter>
</defs>
</svg>
<svg viewBox="0 0 100 100" width="100">
<path d="M0,0 L50,50" stroke="red" filter="url(#shadow1)" />
</svg>

Related

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.

Request part of SVG in <img> (or <object>)

Could I load only part of a SVG (that could be identified by an #id tag) in an <img> or <object> tag ?
Reason (if I need one) is I don't want to have two SVG files if I'm only using part of a already in use one.
If I can't by using these tags, could I by using CSS or other means ?
Please take a look at this svg file in view-source. Inside the root svg there are 2 other svg elements. Those 2 svg elements have an id each and the style is saying that the nestedsvg elements are visible only if :target.
svg > svg:not(:target) {
display: none;
}
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100px" height="100px" viewBox="0 0 225 225">
<style type="text/css">
<![CDATA[
svg > svg:not(:target) {
display: none;
}
]]>
</style>
<desc>
<g id="cat">
<path id="body" fill-rule="evenodd" clip-rule="evenodd" d="M121.506,..."/>
<path id="head" fill-rule="evenodd" clip-rule="evenodd" d="M129.747,..."/>
</g>
</desc>
<svg version="1.1" id="blackcat" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 225 225">
<use xlink:href ="#cat" fill="black" />
</svg>
<svg version="1.1" id="redcat" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 225 225">
<use xlink:href ="#cat" fill="red" />
</svg>
</svg>
Here is how to use one of those svg elements as an image or as an object:
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/222579/cat.svg#redcat" width="200" />
<object width="200" data="https://s3-us-west-2.amazonaws.com/s.cdpn.io/222579/cat.svg#blackcat"></object>
What you want to achieve is easiest done with another inline SVG instead of a <img> or <object> tag:
<svg style="width:200px;height:200px" viewBox="35 50 150 150">
<use xlink:href="myFile.svg#head" />
</svg>
Two things you need to get right are
the viewBox: to get just the part of your SVG that you want, you have to identify where the path is and what bounding box it has. The <use> element takes care that only the element you select is visible, but it does not identify where inside that image the element is.
the overall size your selected element is shown at. SVG has no notion of a "natural size, you always have to give a width and height. The viewBox will then be fitted inside that area.

Why does svg <use xlink:href="#"/> referencing an element with clip-path not work?

When implementing an SVG sprite, an <svg> element is created and svg elements are referenced via the <use> element. The containing <svg> element is then hidden using style="display: none;"
The clip-Path attribute does not render, but the path does. This leaves my path looking different from how I want it to.
How do I use an svg <use xlink:href="#"/> referencing an element with clip-path?
I used grunt-svg-store to create my svg sprite, but have simplified this example for Q&A format https://css-tricks.com/svg-sprites-use-better-icon-fonts/
<svg id="svg-test" style="display: none;">
<clipPath id="my-clip-1">
<circle id="circle-1" cx="50" cy="50" r="50" />
</clipPath>
<path id="svg-test-reference" clip-path="url(#my-clip-1)" d="M10-39.288h80v80H10z" />
</svg>
<!-- Reference SVG <path> by ID with Use -->
<svg class="svg-item" viewBox="0 0 100 100">
<use xlink:href="#svg-test-reference" />
</svg>
Live example on Codepen.io
Use <svg style="width:0; height:0;"> instead of <svg style="display: none;"> to hide the sprite.
<!-- SVG element -->
<svg id="svg-test" style="width:0; height:0;">
<clipPath id="my-clip-1">
<circle id="circle-1" cx="50" cy="50" r="50" />
</clipPath>
<path id="svg-test-reference" clip-path="url(#my-clip-1)" d="M10-39.288h80v80H10z" />
</svg>
<!-- Reference SVG <path> by ID with Use -->
<svg class="svg-item" viewBox="0 0 100 100">
<use xlink:href="#svg-test-reference" />
</svg>
Live example on Codepen.io
In my case, using only "width" and "height" equals to zero, left a void area where should be the image. However, using display:content; instead display:none; works fine, no void area and no image.

Adding clip-path to svg image element that has extra vertical space

I'm having trouble controlling the clip-path coordinates on an image element in a responsive inline svg. Here is my code example (jsfiddle: http://jsfiddle.net/tbbtester/4XP4w/):
CSS:
ul{margin:0;padding:0;list-style:none;height:510px;position: relative;width:1140px;}
li{margin:0;padding:0;position:absolute;background:blue;width:40.1754386%;height:52.15686275%;overflow:hidden;top:0;left:0;}
svg{height: 100%;display:block;width: 100%;position: absolute;top:0;left:0;}
image{clip-path: url(#promo5-1-image);}
HTML:
<ul>
<li>
<svg viewBox='0 0 100 87.59398496' preserveAspectRatio="xMidYMid slice" xml:space="preserve">
<defs>
<clipPath id="promo5-1-image">
<polygon points="0,0 100,0 100,70 0,87.59398496" />
</clipPath>
</defs>
<image preserveAspectRatio="xMinYMid meet" x="0" y="0" width="100" height="87.59398496" xlink:href="http://svgtest.tbb.dev.novicell.dk/test.jpg" src="http://svgtest.tbb.dev.novicell.dk/test.jpg" overflow="visible" />
</svg>
</li>
</ul>
The entire image is visible, nothing has been cut off. But the image element is actually larger than the displayed area - it seems that unnecessary space is added above and below the image, and it causes problems when I want to add a clip-path to it since the point 0,0 starts outside the visible area. (You can see the extra space if you click the image element in the dom in the browsers developer tools)
Ok, I found the solution. The problem was mainly that I used a wrong way of calculating the coordinates of the svg and its elements. Solution fiddle: http://jsfiddle.net/tbbtester/4XP4w/1/
CSS:
ul{margin:0;padding:0;list-style:none;width:100%;height:510px;position: relative;width:1140px;}
li{margin:0;padding:0;position:absolute;background:blue;width:40.1754386%;height:52.15686275%;overflow:hidden;top:0;left:0;}
svg{height: 100%;display:block;width: 100%;position: absolute;top:0;left:0;overflow:hidden;}
image{clip-path: url(#promo5-1-image);}
HTML:
<ul>
<li>
<svg viewBox='0 0 100 58.07860262' preserveAspectRatio="none">
<defs>
<clipPath id="promo5-1-image">
<polygon points="0,0 100,0 100,50.87336245 0,58.07860262" />
</clipPath>
</defs>
<image x="0" y="0" width="100" height="58.07860262" xlink:href="http://svgtest.tbb.dev.novicell.dk/test.jpg" src="http://svgtest.tbb.dev.novicell.dk/test.jpg" />
</svg>
</li>
</ul>

Does element order matter for inline SVG?

In Google Chrome 24, if an element referenced by a <use> element is defined later in the document it isn't rendered. I didn't notice anything related to element order in the documentation for the use element.
Is this behavior undefined and shouldn't be expected to be consistent across browsers or just a bug in Chrome?
An example of this can be seen below (slightly modified from this question). Blue circle renders as expected, red, not so much. Firefox 17 and IE 9 render both circles as I would expect. When the same content is referenced as an external <img />, both circles render as well.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Chrome use-tag bug?</title>
</head>
<body>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="200px" height="200px" viewBox="0 0 200 200">
<defs>
<g id="test2">
<circle cx="50" cy="50" r="25" fill="blue"/>
</g>
</defs>
<g>
<rect x="0.5" y="0.5" width="199" height="199" stroke="black" fill="none"/>
<use xlink:href="#test1" x="0" y="0"/>
<use xlink:href="#test2" x="0" y="0"/>
</g>
<defs>
<g id="test1">
<circle cx="100" cy="100" r="25" fill="red"/>
</g>
</defs>
</svg>
</body>
</html>
UPDATE: Seems to be working in Chrome 39.
The Rendering Order depends on the element order, so it looks strong like a bug in chrome:
SVG Rendering Order 1.0, Part 2: Language