Load external SVG icons into vanilla JavaScript code - javascript

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

Related

Get a single path value for svg file having multiple paths

I am creating an icon component for which I am storing my icons as svg co-ordinates.
Eg:
trash: 'M192 1024h640l64-704h-768zM640 128v-128h-256v128h-320v192l64-64h768l64 64v-192h-320zM576 128h-128v-64h128v64z'
But certain svg icon files have multiple paths(like below). But I intend to have only one path value for storing my icon co -ordinates. Can someone please tell me what can I do to solve this?
<svg id="add-contact-communication-workflow-system-filled" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<g id="Group_822" data-name="Group 822" transform="translate(-938.662 -1091.778)">
<g id="Group_821" data-name="Group 821">
<path id="Path_821" data-name="Path 821" d="M948.432,1103.947a4.063,4.063,0,1,0-4.063-4.062A4.067,4.067,0,0,0,948.432,1103.947Z"/>
<path id="Path_822" data-name="Path 822" d="M948.432,1104.844a7.9,7.9,0,0,0-7.89,7.89,1.539,1.539,0,0,0,1.539,1.54h12.7a1.54,1.54,0,0,0,1.54-1.54A7.9,7.9,0,0,0,948.432,1104.844Z"/>
</g>
<path id="Path_823" data-name="Path 823" d="M960.033,1095.873h-1.84v-1.84a.75.75,0,0,0-1.5,0v1.84h-1.84a.75.75,0,0,0,0,1.5h1.84v1.84a.75.75,0,0,0,1.5,0v-1.84h1.84a.75.75,0,0,0,0-1.5Z"/>
</g>
<rect id="Rectangle_503" data-name="Rectangle 503" width="24" height="24" fill="none"/>
</svg>
here
As long as the paths have the same style, and none of them have a transform different from the others, you can simply append the paths together into one.
M948.432,1103.947a4.063,4.063,0,1,0-4.063-4.062A4.067,4.067,0,0,0,948.432,1103.947Z M948.432,1104.844a7.9,7.9,0,0,0-7.89,7.89,1.539,1.539,0,0,0,1.539,1.54h12.7a1.54,1.54,0,0,0,1.54-1.54A7.9,7.9,0,0,0,948.432,1104.844Z M960.033,1095.873h-1.84v-1.84a.75.75,0,0,0-1.5,0v1.84h-1.84a.75.75,0,0,0,0,1.5h1.84v1.84a.75.75,0,0,0,1.5,0v-1.84h1.84a.75.75,0,0,0,0-1.5Z
Note that the three paths, in your example SVG, are inside a group that has a transform. You may need to take account of that in your component if you want them to display in the correct place.
You have to get rid of transforms and groups first. Then only it can be done. you can try the 'https://www.npmjs.com/package/svgo' module to optimize the svgs and convert them into a single path. But again you have to get rid of groups and transforms.

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.

D3.js Text on path not rendered (no height / width)

I am trying to render circles which have a text inside, that runs along a given path.
The markup d3 produces looks fine, but Chrome is not showing the texts.
Upon inspection it says text elements have 0 width and 0 height.
This is sample markup including only two circles:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 950 600">
<g>
<g transform="translate(334.14792673070184,58.96385042366173)">
<defs>
<path id="path-1" d="m5,50 a45,45 0 0 0 90,0"></path>
</defs>
<circle class="circle" fill="#ccc" cx="50" cy="50" r="50"></circle>
<text fill="#333" font-size="15px">
<textpath xlink:href="#path-1" start-offset="0%">123456</textpath>
</text>
<use xlink:href="#path-1" fill="#666" opacity="0.1"></use>
</g>
</g>
<g transform="translate(374.66047394649974,371.7948729806046)">
<defs>
<path id="path-2" d="m5,50 a45,45 0 0 0 90,0"></path>
</defs>
<circle class="circle" fill="#ccc" cx="50" cy="50" r="50"></circle>
<text fill="#333" font-size="15px">
<textpath xlink:href="#path-2" start-offset="0%">123456</textpath>
</text>
<use xlink:href="#path-2" fill="#666" opacity="0.1"></use>
</g>
</svg>
When I inspect the markup in Chrome console and click "Edit as HTML" on the SVG element, make a random change, save & exit - the SVG suddenly renders correctly.
The exact same thing happens in Firefox and Opera.
Copy pasting the generated markup into a jsfiddle renders everything as expected.
I have tried pulling the < defs > tags out of each individual group into a single global < defs > but it did not solve the problem.
I have also looked at user-agent-stylesheet and other CSS rules that might interfere with rendering.
Is this a problem with how the SVG tag is included and/or the container element's width/height properties? I have been trying different things to fix this for a couple of hours now...
Here is the full SVG markup http://pastebin.com/J2Lz8p23
Here are the relevant parts in my code http://pastebin.com/Bym8kJVN

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

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???? -->

Categories