SVG: <use> element not working when nested in g (with fiddle!) - javascript

I need to display use elements in my SVG graphic.
When I try to use them from within a g element it doesn't work.
In a little demo it looks like the use element works outside of g elements.
Here the fiddle (you can scroll down to the use elements to see the demo):
http://jsfiddle.net/3dacnxdb/2/
Why is it like this? How can i display them from within a g element?
(My graphic is made out of many modules I need the gs to seperate them)
I appreciate any help!
<svg id="graphic">
<defs>
<clipPath id="icon-cp">
<rect x="0" y="0" width="150" height="100" />
</clipPath>
<image id="icon-sprite" width="969" height="293"
xlink:href="http://i.stack.imgur.com/TPx5h.png" />
<g id="icon2" clip-path="url(#icon-cp)">
<use xlink:href="#icon-sprite" transform="translate(-240,0)" />
</g>
</defs>
<!-- ----------------------------------------
Here is the question:
the first use element is not displayed.
The second one is displayed. (the elements are under this text)
Why does it not work?
How can a use element be used within nested g elements?
(to test it you can comment out the second use element,
even tough both use elements have the same attributes
no icon is visible anymore)
---------------------------------------- -->
<!-- following does not work: -->
<g id="testg">
<use xlink:href="#icon2" x="100" y="50" />
</g>
<!-- following works: -->
<use xlink:href="#icon2" x="100" y="50" />
<!-- why???? -->

It does work. Your jsfiddle is not the same as your inline code (the x and y of the use are not the same).
If you make the x and y values in the <g> the same as outside it will work. In the non-working case you are clipping out the contents of the <use> with your clip-path.

You can't see it because they are superposed... =)
<svg id="graphic">
<defs>
<clipPath id="icon-cp">
<rect x="0" y="0" width="150" height="100" />
</clipPath>
<image id="icon-sprite" width="969" height="293" xlink:href="http://i.stack.imgur.com/TPx5h.png" />
<g id="icon2" clip-path="url(#icon-cp)">
<use xlink:href="#icon-sprite" transform="translate(-240,0)" />
</g>
</defs>
<!-- ----------------------------------------
Here is the question:
the first use element is not displayed.
The second one is displayed. (the elements are under this text)
Why does it not work? How can a use element be used within nested g elements?
(to test it you can comment out the second use element, even tough both use elements have the same attributes no icon is visible anymore)
---------------------------------------- -->
<!-- following does not work: -->
<g id="testg">
<use xlink:href="#icon2" x="0" y="0" />
</g>
<!-- following works: -->
<use xlink:href="#icon2" x="100" y="50" />
<!-- why???? -->

Related

Load external SVG icons into vanilla JavaScript code

I am using the OpenWeather API to display the current and five day forecast on my client's website.
I have some custom SVG icons that I want to use instead of the icons provided by OpenWeather. I have implemented the following switch statement to display a different icon depending on the weather condition.
let dailyCondition = value.weather[0].description;
let dailyCondtionIcon = "";
switch (dailyCondition) {
case "clear sky":
dailyConditionIcon = `<svg>icon</svg>`;
break;
case "few clouds":
dailyConditionIcon = `<svg>icon</svg>`;
break;
case "thunderstorm":
dailyConditionIcon = `<svg>icon</svg>`;
break;
case "light rain":
dailyConditionIcon = `<svg>icon</svg>`;
break;
}
Accessing the icons from template literal code works, but with lots of weather conditions in the switch statement, the code is very bloated. I would like to have the SVG icons stored in an external file and loaded from there.
How would I go about loading the external SVG icons into my vanilla JavaScript file?
If you prefer external svg files you could load them in a <use> element.
See also SVG use with External Source
You can combine all svg icons to a single sprite/asset library svg file and then loading each icon individually by a fragment identifier:
You js definitions might look something like this:
dailyConditionIcon = '<svg class="svgInline" fill="red" ><use href="sprite.svg#circle" /></svg>'
.svgAssets{
display:none
}
.svgInline{
display:inline-block;
width:1em;
height:1em;
font-size:32px;
}
<!-- this would be the content of your "sprite.svg" -->
<svg class="svgAssets" xmlns="http://www.w3.org/2000/svg">
<symbol viewBox="0 0 100 100" id="circle">
<circle cx="50%" cy="50%" r="50%"></circle>
</symbol>
<symbol viewBox="0 0 100 100" id="rect">
<rect x="0" y="0" width="100" height="100"></rect>
</symbol>
<symbol viewBox="0 0 100 100" id="rectFixedStyle">
<rect x="0" y="0" width="100" height="100" fill="#ccc"></rect>
</symbol>
</svg>
<!--
for demonstration the filenames are dropped.
The href of a hosted version would be e.g
<use href="sprite.svg#circle" />
-->
<p>
<svg class="svgInline" fill="red" >
<use href="#circle" />
</svg>
<svg class="svgInline" fill="green" >
<use href="#rect" />
</svg>
<svg class="svgInline" fill="green" >
<use href="#rectFixedStyle" />
</svg>
</p>
As you can see you also have some styling abilities like changing fill color.
However the styling options are limited (compared to fully inlined svg) and also depend on your svg structure:
E.g. Styles previously definded in your svg elements can't be overriden by a style set in use/svg tag.
For sprite creation I used: svgsprit.es

How can I apply a pattern to an SVG element that ignores the element's transform values?

I have a JavaScript application that modifies an inline SVG. I have multiple elements within the SVG that all need to have the same background image applied to them. Elements (paths) are positioned in the SVG via transform attributes. Presently I am using a pattern fill on the elements. Is there any way to achieve the effect of the pattern staying stationary irrespective to element transforms?
Currently I have this:
I want this:
(note: I hard-baked the path in the second photo, which can't be used in the application)
Edit:
The patterns are currently applied like so:
<defs>
<pattern id="metallicgold" x="0" y="0" width="1240" height="775" patternUnits="userSpaceOnUse">
<image xlink:href="img/gold-texture.png" />
</pattern>
</defs>
The transforms on elements look like this:
<path transform="translate(-445.0000396775016 -1950.3326958481455) rotate(317.2309439443859 2926.326416015625 2926.32666015625) scale(1 1)" style="fill: url('#metallicgold'); stroke: none;" d="M1951.326416015625,1951.32666015625L3901.326416015625,1951.32666015625L3901.326416015625,3901.32666015625L1951.326416015625,3901.32666015625L1951.326416015625,1951.32666015625Z" x="0" y="0"></path>
If you want a constant background as you transform shapes, you should consider using a filter with userSpaceOnUse filterUnits, unless the transforms are easy and apply to all elements filled, in which case, you can use patternTransform on the pattern to reverse the transform on the elements.
<svg width="800px" height="600px" viewBox="0 0 4000 4000">
<defs>
<filter id="brick" x="0" y="0" width="4000" height="4000" filterUnits="userSpaceOnUse">
<feImage xlink:href="https://s-media-cache-ak0.pinimg.com/originals/c9/97/81/c99781a0ab356681cb038f70b1df68f1.jpg" x="0" y="0" width="4000" height="4000" preserveAspectRatio="xMinYMin meet"/>
<feComposite operator="in" in2="SourceGraphic"/>
</filter>
</defs>
<g filter="url(#brick)">
<path transform="translate(-445.00 -1950.33) rotate(317.23 2926.32 2926.32) scale(1 1)" stroke="none" fill="red" d="M1951.326416015625,1951.32666015625L3901.326416015625,1951.32666015625L3901.326416015625,3901.32666015625L1951.326416015625,3901.32666015625L1951.326416015625,1951.32666015625Z" x="0" y="0"></path>
</g>
<rect x="1250" y="500" width="300" height="300" fill="blue"/>
<g filter="url(#brick)">
<circle cx="1000" cy="1000" r="500"/>
</g>
</svg>

Why doesn't my dynamically created <use> element show?

With JavaScript and jQuery I'm trying to replace a group element with a use element linking to another group element.
// Javascript
origgroup = $("#origgroup")[0];
repgroup = $("#referenceGroup1")[0];
origgroupParent = origgroup.parentNode;
use = document.createElementNS("http://www.w3.org/2000/svg", "use");
use.setAttribute("xlink:href", "#origgroup2");
use.setAttribute("id", "newuse");
tmp = origgroupParent.replaceChild(use, origgroup);
// After this snippet is run, "targetsvg" and "control" are identical. Except that targetsvg's use-tag has an unique ID.
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!-- My "atlas". I want to put <use> elements in "targetsvg" below, linking to these groups. -->
Atlas <br>
<svg id="atlas" width="120" height="70" version="1.1">
<g id="referenceGroup1">
<rect x="10" y="10" width="90" height="20" fill="green"/>
<circle cx="20" cy="40" r="15" fill="blue"/>
</g>
<g id="referenceGroup2">
<rect x="40" y="10" width="90" height="20" fill="red"/>
<circle cx="50" cy="40" r="15" fill="orange"/>
</g>
</svg>
<br> Target <br>
<!-- My target -->
<svg id="targetsvg" width="120" height="70" version="1.1"
xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="origgroup">
<rect x="40" y="10" width="90" height="20" fill="red"/>
<circle cx="50" cy="40" r="15" fill="orange"/>
</g>
</svg>
<br>
Control
<br>
<!-- This is identical to the javascript modified version of "targetsvg" -->
<svg id="control" width="120" height="70" version="1.1"
xmlns:xlink="http://www.w3.org/1999/xlink">
<use xlink:href="#referenceGroup1"></use>
</svg>
What I expect to happen is for there to be a blue circle and a green rectangle under "Target". If I inspect the resulting svg of "targetsvg" it's identical to the svg under "Control". This leads me to believe that "targetsvg" is not redrawn for some reason, is this correct? Is there a way to force that?
I've spent the last five hours searching, but I can't find anything similar. The most relevant I've found is
SVG <use> in Chrome doesn't work
But that uses Angular, which I'm not. I think the cause is the same ("the relative hash link in the element would not correctly resolve."). But if this is the case, how do I resolve this without Angular?
Thanks!
[Background: I have a huge svg-file generated from illustrator. In this file there are a number of fairly complex elements (groups of groups et.c.) that I need to have different versions of. These elements will need to appear on multiple places in the final result, so I either need to have multiple copies of them (Showing/hiding depending on the situation) or some kind of 'atlas' where I pick and replace. My gut says the latter will be more maintainable since there are at least four places and seven "versions" (Think "green", "green with symbol x", "red with symbol y" et.c.). If there are other options, I welcome those.]
Minutes after posting, I realized it was a namespace problem. Changing the JavaScript to:
origgroup = $("#origgroup")[0];
repgroup = $("#referenceGroup1")[0];
origgroupParent = origgroup.parentNode;
// Namespaces
var svgns = 'http://www.w3.org/2000/svg',
xlinkns = 'http://www.w3.org/1999/xlink'
use = document.createElementNS(svgns, "use");
// **setAttributeNS** instead of setAttribute as originally.
use.setAttributeNS(xlinkns, "xlink:href", "#referenceGroup1");
use.setAttribute("id", "newuse");
tmp = origgroupParent.replaceChild(use, origgroup);
Solved my problem.

Is it possible to extend the height and width of a child element beyond its container?

I have a zoomable svg that's provided by 3rd a party API. It's basically a drawing that's mapped to a grid. The problem is the API doesn't include the grid in the provided svg file so I have to hack it to include one. Basically, the markup is like this:
<svg>
<g>
<line>....
<rect>....
etc...
</g>
</svg>
The zoom functionality is done and now I'd like to add the grid. I'm thinking I should add it like so:
<svg>
<g>
<rect x="0" y="0" width="100%" height="100%" fill="black" />
<defs>
<pattern id="grid" width="20" height="20" patternUnits="userSpaceOnUse">
<path d="M 1 1 L 0 0 0 0" fill="none" stroke="green" stroke-width="1" />
</pattern>
</defs>
<rect x="-100%" y="-100%" width="200%" height="200%" fill="url(#grid)" />
<line>....
<rect>....
etc...
</g>
</svg>
The grid is drawn but it's bound within the <g> tag. Is it possible to extend the grid beyond it but still zoomable? Or are there other better ways to achieve this?

Toggle layer in SVG

I am trying to create a HTML page with a check list that we can also make it large without losing quality. As a result I want to use SVG.
I would like to have a script to operate on the SVG so that I can toggle the group svg_2 (a check mark) on or off so that we have checked and unchecked box. It doesn't have to change when loaded, just need like an inline command that will do it.
<svg width="20%" height="20%" xmlns="http://www.w3.org/2000/svg">
<rect id="svg_1" fill="#ffffff" stroke="#000000" stroke-width="10%" x="2.5%" y="2.5%" width="85%" height="85%" />
<g id="svg_2">
<line fill="none" stroke="#ff0000" stroke-width="10%" x1="43.5%" y1="77.5%" x2="10.5%" y2="49.5%" id="svg_3" stroke-linecap="round" stroke-linejoin="bevel"/>
<line fill="none" stroke="#ff0000" stroke-width="10%" x1="95%" y1="9.5%" x2="44.5%" y2="78.5%" id="svg_4" stroke-linecap="round" stroke-linejoin="bevel"/>
</g>
</svg>
You can use JavaScript to toggle the svg_2 on or off depending on its previous state (example using JQuery):
$("svg").click(function() {
if ( $('#svg_2').css('visibility') == 'hidden' )
$('#svg_2').css('visibility','visible');
else
$('#svg_2').css('visibility','hidden');
});
You could also use some other CSS attribute (such as display).
See and try it here: JSFiddle

Categories