Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I refer to an internal gradient definition inside an SVG sprite symbol?

Tags:

css

gradient

svg

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):

firefox fills as white

whilst Chrome renders it in black:

chrome fills as 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.

like image 877
Matt Morrison Avatar asked Jul 17 '14 14:07

Matt Morrison


People also ask

How do you find gradient in SVG?

To use a gradient, we have to reference it from an object's fill or stroke attributes. This is done the same way you reference elements in CSS, using a url . In this case, the url is just a reference to our gradient, which I've given the creative ID, "Gradient". To attach it, set the fill to url(#Gradient) , and voila!

Can you have gradient in SVG?

SVG provides for two types of gradients: linear gradients and radial gradients. Once defined, gradients are then referenced using 'fill' or 'stroke' properties on a given graphics element to indicate that the given element shall be filled or stroked with the referenced gradient.

How do you make a SVG gradient in CSS?

The x1, x2, y1,y2 attributes of the <linearGradient> tag define the start and end position of the gradient. The color range for a gradient can be composed of two or more colors. Each color is specified with a <stop> tag. The offset attribute is used to define where the gradient color begin and end.

How do I use SVG sprites?

To create a sprite in SVG we use the <symbol> tag and apply an ID for referencing later and the viewBox attribute for defining the canvas size. Inside of the symbol icon we create our shapes, text and any other elements that make up our icon.


2 Answers

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.

like image 64
Matt Morrison Avatar answered Oct 06 '22 10:10

Matt Morrison


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>
like image 36
Robert Longson Avatar answered Oct 06 '22 09:10

Robert Longson