I need to create a number of SVG files and would love to keep a set of common symbols in one file and import them into the others.
I managed to do this using a <use> element:
<use href="common.svg#symbol1" />
The problem is that if common.svg has a CSS style that affects an element, the style has no effect in the file where the element is imported.
I uploaded two SVGs on svgur.com to show this:
https://svgur.com/i/bYv.svg
...defines a circle with id ball affected by a style that sets a red border around it.
<svg width="100" height="100" version="1.1" xmlns="http://www.w3.org/2000/svg">
<style>#ball { stroke:#ff0000; stroke-width:10; }</style>
<circle id="ball" cx="50" cy="50" r="45" />
</svg>
https://svgur.com/i/bXA.svg
...uses the circle. The circle is visible but the border is not.
<svg width="100" height="100" version="1.1" xmlns="http://www.w3.org/2000/svg">
<use href="bYv.svg#ball" />
</svg>
Questions:
Is this a bug of the SVG renderer I'm using or is it how it's supposed to behave?
Every renderer I tried (chrome, firefox, inkscape) shows the same result so I suspect this might be the intended behavior.
Is there any way to import an element from an external SVG file and the CSS styles that affect it too, so that it appears exactly like it does in its original document?
Is this a bug of the SVG renderer I'm using or is it how it's supposed to behave?
It is the intended behaviour. CSS rules do not apply across document boundaries. You are importing the SVG into your main document, but your CSS is in another document.
Is there any way to import an element from an external SVG file and the CSS styles that affect it too...
No.
Well I suppose you could technically write some Javascript to load the other file and extract the CSS rules. But I strongly suspect you don't want to do that.
You SVG "sprite document" should not have CSS rules in a <style> tag.
The best approach is to pre-prepare you SVGs to be used as sprites.
What I would do is import your common.svg into a vector editor and convert all your CSS attributes into presentation attributes. For example Illustrator lets you choose the method of styling when you export an SVG.
What you want is for something like this:
<svg>
<style>
.st0 {
fill: red;
}
</style>
<symbol id="whatever">
<path d="..." class="st0"/>
</symbol>
</svg>
to be converted to:
<svg>
<symbol id="whatever">
<path d="..." fill="red"/>
</symbol>
</svg>
According to the Scalable Vector Graphics (SVG) 2
specification (W3C Editor’s Draft, 08 June 2021) it seems that the style of the original document should be applied where an element is imported.
Section 5.5. The ‘use’ element:
The cloned content inherits styles from the ‘use’ element and can be the target of user events. However, these cloned element instances remain linked to the referenced source and reflect DOM mutations in the original. In addition, all style rules that apply in the scope of the referenced element also apply in the scope of the cloned shadow tree.
And 5.5.3. Style Scoping and Inheritance:
The use-element shadow tree, like other shadow trees, exhibits style encapsulation, as defined in the CSS Scoping module [css-scoping-1]. This means that elements in the shadow tree inherit styles from its host ‘use’ element, but that style rules defined in the outer document do not match the elements in the shadow tree. Instead, the shadow tree maintains its own list of stylesheets, whose CSS rules are matched against elements in the shadow tree.
When the referenced element is from an external document, the stylesheet objects generated when processing that document apply to the shadow tree, and are read-only. All URL references in the stylesheet, including fragment-only references, must be made absolute, relative to the URL of the document that contains the referenced element. User agents may re-use the same stylesheet objects for any shadow trees that reference that same external document.
So it's the browsers and SVG renderers I tried that are not conforming to the standard.
I'm still looking for a way to emulate this behavior on existing SVG user agents.
Your styles will be applied, provided your svg asset code is inlined
<svg style="display:none" class="svg-asset" width="100" height="100" version="1.1" xmlns="http://www.w3.org/2000/svg">
<style>
#inline-ball { stroke:#ff0000; stroke-width:10; }
</style>
<circle id="inline-ball" cx="50" cy="50" r="45" />
</svg>
<svg width="100" height="100" version="1.1" xmlns="http://www.w3.org/2000/svg">
<use xlink:href="#inline-ball" />
</svg>
If you prefer not to inline svg code then "fragment identifiers" might be the better embedding approach.
It's basically a sprite concept:
So your image assets(like symbols) need to be positioned with some x or y offset on sprite pane (unlike symbols who could be positioned with overlapping).
You're actually loading a complete svg including embedded css style elements but choose a particular view frame.
See this articel on css.tricks.com
css.tricks.com: How SVG Fragment Identifiers Work
Your svg would be something like this
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 32 96" >
<g id="icon01">
<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="icon02">
<path d="M32,43.2c0,2.7-1.2,5.1-3,6.8l0,0l-10,10c-1,1-2,2-3,2s-2-1-3-2l-10-10c-1.9-1.7-3-4.1-3-6.8c0-5.1,4.1-9.2,9.2-9.2c2.7,0,5.1,1.2,6.8,3c1.7-1.9,4.1-3,6.8-3C27.9,33.9,32,38.1,32,43.2z"/>
</g>
<view id="icon-clock-view" viewBox="0 0 32 32" />
<view id="icon-heart-view" viewBox="0 32 32 32" />
</svg>
For using the fragment identifiers we need to add <view> elements with corresponding sprite area coordinates (defined in viewBox attribute).
Unlike symbol definitions we don't nest the actual path data in view elements.
Embedding in html works by adding the target ID to our src url.
<img class="svg-fragment" src="fragment-ready-1.svg#icon-heart-view">
However, inlining svg graphics in your html, still offers the best styling controls.
(e.g styling elements in your websites css file)
Maybe we'll see additional support for styling remotely embedded svg assets in the future ... Until then we still have to cope with some browser quirks.
I have the following nested html elements :
<div class="trend-line-chart">
<div class="title"></div>
<div class="chart">
<div class="">
<svg width="200" height="100">
<g>
<g>
<g>
<path></path>
<path></path>
<path></path>
<g>
<defs>
<lineargradient id="LineChartGradient" gradientTransform="rotate(90)">
<stop offset="10%" stop-color="#D6EBF3"></stop>
<stop offset="10%" stop-color="#D6EBF3"></stop>
</lineargradient>
</defs>
<polygon points="...." stroke="#8AB9E1" stroke-width="0" fill="url(/dashboard/renderer#LineChartGradient)" fill-opacity="0.5"></polygon>
</g>
</g>
</g>
</g>
</svg>
</div>
</div>
</div>
This component is within a larger html file with lots of other components.
At first the fill gradient didn't work until I added /dashboard/renderer to its URL path (which you can see in the code above) . Now , once again it's not working. As we are developing a dashboard , we move pages to different paths. And I'm sure the reason of not showing up is the the url. Any idea on how to refer to the fill gradient locally ? So regardless of the path it always works.
Does you HTML header have a <base> element? If so, it will interfere with how the browser interprets gradient URLs.
If you need to keep the <base> element, then the solution is to use an absolute URL (as you seem to have discovered).
fill="url(/path/to/my/HTML/file#LineChartGradient)"
You say it has stopped working. Has the URL of your page changed recently?
SVG Gradients are defined in the document with a unique id attribute, and then referenced from another element as a URL. Currently, our AngularJS code uses html base tag which stop the SVG Gradient from working. The reason is that the url is not relative to the current document anymore but they are computed relative to the specified separate URI.
It looks like that there's a fix for this bug in AngularJS : https://github.com/angular/angular.js/issues/8934#issuecomment-56568466
References:
[1] SVG Gradient turns black when there is a BASE tag in HTML page?
[2] Angular and SVG filters
SUMMARY: An SVG sprite contains five icon <symbol> blocks, one of which references its own gradient definition by ID. It is no longer able to find this gradient and render properly.
JSFIDDLE: http://jsfiddle.net/Qtq24/1/
I am switching some graphics to SVG, and being that they are icons (in this case for social networking profiles) I'd like to keep them in a sprite (as I had with PNG before).
I've followed this guide to SVG sprites on CSS-tricks.com (along with this follow-up which advises using <symbol> instead of <g>).
I now have an SVG sprite file, social-sprite.svg, which you can view in full here.
This is one complete <svg> block containing five different <symbol> blocks, each with an id and with a viewBox attribute. In each case I got the SVG code for each symbol by preparing official icons in Adobe Illustrator and retaining the relevant parts of the processed code.
The .svg file is included via PHP as soon as the <body> tag opens (and this is why the main <svg> container inside it is marked with style="display: none;") so that the references to each symbol work from the HTML.
Four icons work perfectly, and the only one I am having trouble with is the YouTube icon, because it uses an internally-defined gradient. Here is the YouTube part of the SVG code:
<symbol id="youtube" viewBox="0 0 400 281.641">
<path id="Triangle" fill="#FFFFFF" d="M159.845,191.73l106.152-54.999L159.845,81.348V191.73z"/>
<path id="The_Sharpness" opacity="0.12" fill-rule="evenodd" clip-rule="evenodd" fill="#420000" d="M159.845,81.348l93.091,62.162
l13.061-6.778L159.845,81.348z"/>
<g id="Lozenge">
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="200.4204" y1="2.6162" x2="200.4204" y2="278.9292">
<stop offset="0" style="stop-color:#E52D27"/>
<stop offset="1" style="stop-color:#BF171D"/>
</linearGradient>
<path fill="url(#SVGID_1_)" d="M392.928,62.226c0,0-3.839-27.073-15.617-38.995C362.371,7.583,345.626,7.506,337.947,6.59
c-54.975-3.974-137.441-3.974-137.441-3.974h-0.171c0,0-82.464,0-137.44,3.974c-7.68,0.916-24.419,0.993-39.364,16.641
C11.753,35.153,7.92,62.226,7.92,62.226s-3.929,31.792-3.929,63.583v29.805c0,31.791,3.929,63.582,3.929,63.582
s3.833,27.073,15.611,38.995c14.945,15.646,34.575,15.152,43.318,16.792c31.43,3.015,133.571,3.946,133.571,3.946
s82.552-0.124,137.526-4.099c7.679-0.915,24.424-0.993,39.363-16.64c11.778-11.922,15.617-38.995,15.617-38.995
s3.923-31.791,3.923-63.582v-29.805C396.851,94.017,392.928,62.226,392.928,62.226z M159.863,191.73l-0.018-110.383
l106.152,55.384L159.863,191.73z"/>
</g>
</g>
</symbol>
And this is called in the HTML with:
<svg width="30" height="21">
<use xlink:href="#youtube" src="fallback.png" width="30" height="21" />
</svg>
The opening two paths work fine, the problem is that in this new combined sprite SVG file, with each icon separated as a <symbol>, the "Lozenge" <path> is unable to find the #SVGID_1_ reference to the <linearGradient>.
In Firefox this causes the lozenge to display as white (I assume, perhaps it is not displaying at all - not really looked into it):
whilst Chrome renders it in black:
Obviously neither is acceptable. The only thing I can do at the moment is remove fill="url(#SVGID_1_)" on the path and just fill with a flat colour red appropriate to the YouTube logo. This is not a proper solution though, even discounting the fact that bastardising the YouTube logo in this way would not be accepted under their brand guidelines.
Things I've tried (and had no luck with):
removing the two <g> wrappers that surround the gradient and the path, so the whole of the symbol is just <path>-<path>-<linearGradient>-<path>
wrapping the gradient definition inside a <defs> container
wrapping it in a <defs> and also moving it to the top of the SVG file, i.e. outside the bounds of the YouTube-specific <symbol>
changing ID name (you never know!)
redefining the gradient with percentages rather than pixel values
So how do I get an already-internal <symbol> to reference an also-internal <linearGradient> definition?
EDIT: It turns out the gradient fails when the whole <svg> block is marked with style="display: none;". If this style is removed, the gradient renders properly. But as a reminder, this styling is added so that when you import the SVG sprite it is not rendered instantly on the page, and just allows you to make references to the id-defined symbols as required.
visibility: hidden or opacity: 0 both allow the gradient to render properly, obviously they don't offer proper solutions though as they still demarcate the space that the SVG would have taken up if visible.
After discovering all this, I was pretty sure it would be no problem to have the fully visible <svg> with no stylings added INSIDE a container <div> which is hidden. However, even this causes the gradient not to render. I'm no closer to solving the issue.
Firstly please note the edit to my question - whereupon I discover that the use of display: none to hide the SVG symbols until we need them was the problem.
I kept fiddling and settled upon this "answer", which is far from perfect, but should still be reliable for any such situation.
All you need to do is wrap the entire <svg> code in a <div> container which must be displayed but will never affect layout, so I've just done this via mega overkill CSS such as:
height: 0; width: 0; position: absolute; visibility: hidden;
And this works great. See the final fiddle: http://jsfiddle.net/Qtq24/5/
If anyone has a better solution, I'd love to hear it as this feels like a bit of a hacky way of doing it but I guess no more hacky than having to use display: none; anyway.
Don't use style="display: none;" in SVG. You have it on the root <svg> element. Either visibility:hidden, height/width="0" or <defs> are better alternatives.
There used to be a bug in Firefox with gradient elements in symbols. That bug was fixed many versions ago now. The original code works as expected.
<svg width="30" height="21">
<symbol id="youtube" viewBox="0 0 400 281.641">
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="200.4204" y1="2.6162" x2="200.4204" y2="278.9292">
<stop offset="0" style="stop-color:#E52D27"/>
<stop offset="1" style="stop-color:#BF171D"/>
</linearGradient>
<path id="Triangle" fill="#FFFFFF" d="M159.845,191.73l106.152-54.999L159.845,81.348V191.73z"/>
<path id="The_Sharpness" opacity="0.12" fill-rule="evenodd" clip-rule="evenodd" fill="#420000" d="M159.845,81.348l93.091,62.162
l13.061-6.778L159.845,81.348z"/>
<g id="Lozenge">
<g>
<path fill="url(#SVGID_1_)" d="M392.928,62.226c0,0-3.839-27.073-15.617-38.995C362.371,7.583,345.626,7.506,337.947,6.59
c-54.975-3.974-137.441-3.974-137.441-3.974h-0.171c0,0-82.464,0-137.44,3.974c-7.68,0.916-24.419,0.993-39.364,16.641
C11.753,35.153,7.92,62.226,7.92,62.226s-3.929,31.792-3.929,63.583v29.805c0,31.791,3.929,63.582,3.929,63.582
s3.833,27.073,15.611,38.995c14.945,15.646,34.575,15.152,43.318,16.792c31.43,3.015,133.571,3.946,133.571,3.946
s82.552-0.124,137.526-4.099c7.679-0.915,24.424-0.993,39.363-16.64c11.778-11.922,15.617-38.995,15.617-38.995
s3.923-31.791,3.923-63.582v-29.805C396.851,94.017,392.928,62.226,392.928,62.226z M159.863,191.73l-0.018-110.383
l106.152,55.384L159.863,191.73z"/>
</g>
</g>
</symbol>
<use xlink:href="#youtube" width="30" height="21" />
</svg>
I'm just starting on svg and trying to figure out the limitations of styling.
I have an svg text element in svg. Some of my text elements are tagged with data-editable="true".
I would like for users to be able to easily see what the tagged elements are. I'm imagining a simple, toggleable, translucent overlay.
When I hover over the element in the DOM view in chrome the text element is rendered with a blue-ish overlay. I would like to get a similar effect or something that is somewhat close.
I'd prefer using css if that is possible but any way of achieving that effect would be good. Worst comes to worst I can hack something with inserting rect elements using d3 and jquery but that seems quite messy.
Building on top of an answer by Erik Dahlström
<svg width="100%" height="100%" viewBox="0 0 500 140" preserveAspectRatio="xMidYMid meet">
<style type="text/css">
*[data-editable=true]:hover{ filter:url(#highlight) }
</style>
<filter x="0" y="0" width="1" height="1" id="highlight">
<feFlood flood-color="rgba(100,200,0,.5)"/>
<feComposite in="SourceGraphic"/>
</filter>
<text data-editable="true" y="100" font-size="100">test</text>
</svg>
This works with plain CSS and a filter definition.
The SVG spec talks about Properties.. what are these? Can they be declared as attributes inline with the element? .. or can they only be declared in CSS stylesheets?
They can be set both inline and in a stylesheet, but to be standards compliant I would opt for declaration via an external stylesheet
Standards compliant are both. There are several reasons why to use the one or the other.
The spec says, that CSS style declared properties always take precedence before the ones declared in XML attributes
On the other hand, if you use attributes, you don't have the hassle to parse CSS declarations
you can also declare an external stylesheet and style yur SVG from there
Styling properties, in short, are all these props, that are necessary for a certain rendering result, mostly related to color.
Equivalent examples:
<svg xmlns="http://www.w3.org/2000/svg">
<rect fill="red"/>
<svg>
<svg xmlns="http://www.w3.org/2000/svg">
<rect style="fill: red"/>
<svg>
<svg xmlns="http://www.w3.org/2000/svg">
<defs>
<style type="text/css">
#fillme { fill: red; }
</style>
</defs>
<rect id="fillme"/>
<svg>
Just note, that these CSS declarations are not valid in the sence of CSS specs 1 through 3.
Cheers,