How to change the href of this SVG sprite using vanilla JavaScript? - javascript

I am using an SVG sprite sheet and I am trying to change the href of the svg using vanilla javascript, but after researching a solution, have run into a brick wall.
html
let scores, roundScore, activePlayer, dice, diceSvg, diceImg, diceHrefString;
scores = [0,0];
roundScore = 0;
activePlayer = 0;
document.querySelector(`#p${activePlayer}c-score`).textContent = dice;
diceSvg = document.getElementById("dice-icon");
diceSvg.style.display = "none";
document.getElementById("roll-dice").addEventListener("click",function(){
dice = Math.floor(Math.random() * 6) + 1;
diceImg = document.querySelector(".dice-icon");
diceHrefString = `dice_sprite_sheet.svg#dice-${dice}`;
if(dice !== 0){
diceImg.setAttributeNS("xlink:","href",diceHrefString);
}else{
diceImg.setAttributeNS("xlink:","href","dice");
}
diceSvg.style.display = "block";
});
<svg class="dice-icon" id="dice-icon">
<use xlink:href="dice_sprite_sheet.svg#dice"></use>
</svg>

As I've commented: you need to use the svg xlink namespace: http://www.w3.org/1999/xlink. When you change the value of the xlink:href dynamically this is how you do it: theUse.setAttributeNS(SVG_XLINK, 'xlink:href', '#theId');
This is an example:
const SVG_XLINK = "http://www.w3.org/1999/xlink";
theUse.setAttributeNS(SVG_XLINK, 'xlink:href', '#spade');
svg{border:1px solid}
<svg class="dice-icon" id="dice-icon" viewBox="0 0 20 20" width="200" height="200">
<use id="theUse" xlink:href="#heart"></use>
</svg>
<svg width="0" height="0" display="none">
<title>symbols defs</title>
<defs>
<symbol viewBox="0 0 20 20" id="spade" style="overflow: visible">
<title>Spade</title>
<path d="M9,15C9,20 0,21 0,16S6,9 10,0C14,9 20,11 20,16 S11,20 11,15Q11,20 13,20H7Q9,20 9,15Z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="heart" style="overflow: visible">
<title>heart</title>
<path d="M10,6 Q10,0 15,0T20,6Q20,10 15,14 T10,20Q10,18 5,14T0,6Q0,0 5,0T10,6Z"/>
</symbol>
</defs>
</svg>
I hope it helps.

Related

Dynamic percentage mask in SVG (for 5-star-reviews)

I'm creating a 5-star-review using SVG, but need to be able to display a value to 1-decimal-point (e.g. 2.5/5 or 4.2/5).
I also need to be able to display multiple 5-star-reviews on the same page.
I've been able to create the following (which displays 2.5) but the 50% on the second <rect> in the mask (which is required for the 0.5 on the 3rd star) is hardcoded...
<svg width="0" height="0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<defs>
<mask id="partial">
<rect x="0" y="0" width="64" height="64" fill="white" />
<rect x="50%" y="0" width="64" height="64" fill="black" />
</mask>
<symbol xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" id="star">
<path d="M32 2l-10 22l-20 2l15 14l-5 22l20-12l20 12l-5-22l15-14l-20-2Z"
stroke-width="3" stroke="#FFC800" />
</symbol>
</defs>
</svg>
<svg width="32" height="32" viewBox="0 0 64 64">
<use href="#star" fill="#FFC800"></use>
<use href="#star" fill="none"></use>
</svg>
<svg width="32" height="32" viewBox="0 0 64 64">
<use href="#star" fill="#FFC800"></use>
<use href="#star" fill="none"></use>
</svg>
<svg width="32" height="32" viewBox="0 0 64 64">
<use href="#star" fill="#FFC800" mask="url(#partial)"></use>
<use href="#star" fill="none"></use>
</svg>
<svg width="32" height="32" viewBox="0 0 64 64">
<use href="#star" fill="none"></use>
</svg>
<svg width="32" height="32" viewBox="0 0 64 64">
<use href="#star" fill="none"></use>
</svg>
Is there any way (preferably without javascript/jquery) that I can set the x="50%" on the <use> element?
Please remember that I will have multiple instances of the 5-star-review on the page at the same time, so it's not possible for me to dynamic set the x=50% on the <mask> because it will effect all instances.
What I don't want is to have to have 9 different id="partial10", id="partial20" <mask> elements.
I would like to do something like...
<use href="#star" fill="#FFC800" maskwidth="30%"></use>
If you want multiple instances on one page, then go for a native Web Component (supported in all browsers)
All HTML required:
<star-rating stars=5 rating="3.5"
bgcolor="green" nocolor="grey" color="gold"></star-rating>
<star-rating stars=7 rating="50%"
bgcolor="rebeccapurple" nocolor="beige" color="goldenrod"></star-rating>
to create:
But it does require JavaScript to create the Web Component
Key is to draw the rating color behind cutouts of all stars
Needs minor work to make 51% notation possible,
add more <rect> elements, it now does 2 for each star, and set the correct width for each mouseover capturing <rect>
The beauty of Web Components is: It totally does NOT matter when the <star-rating> is defined.
customElements.define("star-rating", class extends HTMLElement {
set rating(rate) {
if (!String(rate).includes("%")) rate = Number(rate) / this.stars * 100 + "%";
this.querySelector(":nth-child(2)").setAttribute("width", rate); //2nd rect
}
connectedCallback() {
let {bgcolor,stars,nocolor,color,rating} = this.attributes;
let repeat = (count, func) => Array(count).fill().map(func);
this.stars = ~~stars.value || 5;
this.innerHTML = `<svg viewBox="0 0 ${this.stars*100} 100" style=cursor:pointer>` +
`<rect height=100 fill=${nocolor.value} width=100% />` +
`<rect height=100 fill=${color.value} />` +
repeat(this.stars , (i, n) => `<path fill=${bgcolor.value} d="m${ n*100 } 0h102v100h-102v-100m91 42a6 6 90 00-4-10l-22-1a1 1 90 01-1 0l-8-21a6 6 90 00-11 0l-8 21a1 1 90 01-1 1l-22 1a6 6 90 00-4 10l18 14a1 1 90 010 1l-6 22a6 6 90 008 6l19-13a1 1 90 011 0l19 13a6 6 90 006 0a6 6 90 002-6l-6-22a1 1 90 010-1z"/>`) +
repeat(this.stars * 2, (i, n) => `<rect x=${ n*50 } n=${n} opacity=0 width=50 height=100 ` +
` onclick="this.closest('star-rating').dispatchEvent(new Event('click'))" ` +
` onmouseover="this.closest('star-rating').rating=${(n+1)/2}"/>`) +
"</svg>";
this.rating = rating.value;
}
});
<star-rating stars=5 rating="3.5"
bgcolor="green" nocolor="grey" color="gold"></star-rating>
<br>
<star-rating stars=7 rating="50%"
bgcolor="rebeccapurple" nocolor="beige" color="goldenrod"></star-rating>

Scripted SVG animation no-longer working in some browsers

I am trying to work out why the code below no-longer works in Firefox or Chrome.
The files were last modified over ten years ago.
The static svg dispays, but the script does not run. To my considerable surprise, it does work as it should in Edge, as does a more complicated diagram with interactive elements.
There is probably some obscure setting I need to doctor in Firefox, but I don't know where to look. I don't know when I last tried one of these files, but I would be fairly sure they still worked a couple of years ago.
The code is probably full of daftnesses, as I have done very little javascript, and I probably should now be using requestanimationframe, but that is not the point - it has worked, and still does in Edge.
(Question edited to remove link to irrelevant SMIL version of the animation.)
This is the html file:
<html>
<head>
<title>
SVG slider-crank animated by script
</title>
</head>
<body onload="main()">
<script type="text/javascript">
<!--
var svgdoc = null;
var crank = null;
var crosshead = null;
var conrod = null;
var pi = Math.PI;
function main()
{
var timer = null;
var angle = 0;
var diagram = document.getElementById('svg');
if (diagram && diagram.contentDocument)
{
svgdoc = diagram.contentDocument;
}
else
{
try
{
svgdoc = diagram.getSVGDocument();
}
catch(exception)
{
alert("Unable to get SVG document");
}
}
crank = svgdoc.getElementById('ShowCrank');
crosshead = svgdoc.getElementById('ShowCrosshead');
conrod = svgdoc.getElementById('ShowConRod');
timer = setInterval(function(){(angle = rotation(angle))}, 25);
}
function rotation(angle)
{
var step = 3;
var theta = angle * pi / 180;
var alpha = Math.asin(Math.sin(theta) / 5);
var offset = 100 * (Math.cos(theta) -1) - 500 * (Math.cos(alpha) - 1);
crank.setAttributeNS(null, 'transform', ("rotate(" + angle + ", 800, 300)"));
crosshead.setAttributeNS(null, 'transform', ("translate(" + offset + ", 0)"));
conrod.setAttributeNS(null, 'transform', ("translate(" + offset + ", 0) rotate(" + (alpha * 180 / pi) + ", 400, 300)"));
angle = angle < 360 - step ? angle + step : 0;
return angle;
}
-->
</script>
<object id="svg" type="image/svg+xml" data="Slider_Crank.svg" width="1200" height="800">
<param name="src" value="Slider_Crank.svg">
</object>
</body>
</html>
This is the svg file:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1"
baseProfile="full"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:ev="http://www.w3.org/2001/xml-events"
width="1200"
height="800">
<title> Slider-Crank </title>
<defs>
<rect
id="Slidebar"
stroke-width="1"
stroke="black"
fill="silver"
fill-opacity="1"
x="0"
y="-12"
width="300"
height="24"
/>
<g id="Crosshead" stroke-width="1" stroke="black" fill-opacity="1">
<rect
fill="gold"
x="-50"
y="-25"
width="100"
height="50"
/>
<circle cx="0" cy="0" r="15" fill="white"/>
</g>
<g id="Crank" stroke-width="1" stroke="black" fill-opacity="1">
<path fill="silver"
d="M 99.959 40.000
A 40 40 0 0 0 99.959, -40.000
A 450 450 0 0 1 9.950, -49.000
A 50 50 0 1 0 9.950, 49.000
A 450 450 0 0 1 99.959, 40.000
z"/>
<circle cx="100" cy="0" r="25" fill="white"/>
<circle cx="0" cy="0" r="30" fill="lightgrey"/>
</g>
<g id="ConRod" stroke-width="1" stroke="black" fill-opacity="0.7">
<path fill="silver"
d="M 12.387 21.715
A 30 30 0 0 1 27.551 17.776
L 453.475 22.035
A 30 30 0 0 1 473.243 29.733
A 40 40 0 0 1 473.243 -29.733
A 30 30 0 0 1 453.475 -22.035
L 27.551 -17.776
A 30 30 0 0 1 12.387 -21.715
A 25 25 0 0 1 12.387 21.715
z"/>
<circle cx="0" cy="0" r="25" fill="silver"/>
<circle cx="0" cy="0" r="15" fill="white"/>
<circle cx="500" cy="0" r="40" fill="silver"/>
<circle cx="500" cy="0" r="25" fill="white"/>
</g>
</defs>
<use id="ShowTopSlidebar" xlink:href="#Slidebar" x="150" y="263"/>
<use id="ShowBottomSlidebar" xlink:href="#Slidebar" x="150" y="337"/>
<use id="ShowCrosshead" xlink:href="#Crosshead" x="400" y="300"/>
<use id="ShowCrank" xlink:href="#Crank" x="800" y="300"/>
<use id="ShowConRod" xlink:href="#ConRod" x="400" y="300"/>
</svg>
Thanks to Robert Longson: Firefox about:config setting 'security.fileuri.strict_origin_policy'

Combine two SVG elements into one using Javascript?

Let's say I have the following html context, which I don't have access to its creation :
<div id="outer">
<div id="chart-div">
<svg id="chart"></svg>
</div>
<div id="legend-div">
<svg id="legend"></svg>
</div>
</div>
What I'm trying to do is export this SVG to an image using canvg library, the problem here is that they are separated and so I get two canvas, and canvg library accepts a SVG definition string as input.
How can I modify the html elements above so that they are only one SVG? Using Javascript as this is a browser extension.
I have tried just switching the DIVs tags to SVG but it just broke everything and the SVG became blank
This is my solution to your problem: I make the outer div display:none, I'm creating another svg element (you may do it dynamicaly) end inside the #new svg I'm using the #chart and the #legend. I hope it helps.
svg{border:1px solid;}
#outer {display:none}
#outer div{position:absolute; width:500px;}
<div id="outer">
<div id="chart-div">
<svg id="chart" viewBox="0 0 300 150">
<circle stroke="gold" fill="none" cx="100" cy="75" stroke-width="40" stroke-dasharray="124.85" stroke-dashoffset="20" r="20" />
</svg>
</div>
<div id="legend-div">
<svg id="legend" viewBox="0 0 300 150">
<rect fill="skyBlue" x="200" y="100" width="80" height ="30" />
</svg>
</div>
</div>
<svg id="new" viewBox="0 0 300 150" width="500">
<use xlink:href="#chart" />
<use xlink:href="#legend" />
</svg>
This is a Javascript solution for merging two svg accessibles in the document through DOM manipulation.
var svgNS = "http://www.w3.org/2000/svg";
var outer = document.getElementById('outer');
// get chart content
var chart = document.getElementById('chart-div');
var chartSvg = chart.getElementsByTagName('svg')[0];
var chartContent = Array.from(chartSvg.childNodes);
// get legend content
var legend = document.getElementById('legend-div');
var legendSvg = legend.getElementsByTagName('svg')[0];
var legendContent = Array.from(legendSvg.childNodes);
// create a merged-div where we are going to merge the svgs
var merged = document.createElement('div');
merged.setAttribute('id', 'merged-div');
outer.appendChild(merged);
// createElementNS for svg
var mergedSvg = document.createElementNS(svgNS, 'svg');
mergedSvg.setAttribute('id', 'merged');
// keep the viewBox of the chart
mergedSvg.setAttribute('viewBox', chartSvg.getAttribute('viewBox'));
merged.appendChild(mergedSvg);
// adding the content of both svgs
for (let i = 0; i < chartContent.length; i++) {
mergedSvg.appendChild(chartContent[i]);
}
for (let i = 0; i < legendContent.length; i++) {
mergedSvg.appendChild(legendContent[i]);
}
// the unmerged svgs can be removed
chart.remove();
legend.remove();
<div id="outer">
<div id="chart-div">
<svg id="chart" viewBox="0 0 300 150">
<circle stroke="gold" fill="none" cx="100" cy="75" stroke-width="40" stroke-dasharray="124.85" stroke-dashoffset="20" r="20" />
</svg>
</div>
<div id="legend-div">
<svg id="legend" viewBox="0 0 300 150">
<rect fill="skyBlue" x="200" y="100" width="80" height ="30" />
</svg>
</div>
</div>
Resulting markup:
<div id="outer">
<div id="merged-div">
<svg id="merged" viewBox="0 0 300 150">
<circle stroke="gold" fill="none" cx="100" cy="75" stroke-width="40" stroke-dasharray="124.85" stroke-dashoffset="20" r="20"></circle>
<rect fill="skyBlue" x="200" y="100" width="80" height="30"></rect>
</svg>
</div>
</div>

Clone certain group of elements from external SVG and use that group to create another SVG

I'm trying to use only certain group of elements from external SVG file(icons.svg) and use the selected group to create another SVG tag. The reason for trying to have it this way is that I don't want to have separate SVG file for every single icon as there will be tons of them eventually.
I prefer using D3 and currently my HTML code is following (which doesn't work):
<!DOCTYPE html>
<meta charset="utf-8">
<head><script src="https://d3js.org/d3.v3.js"></script></head>
<body>
<script>
d3.xml("icons.svg").mimeType("image/svg+xml").get(function(error, xml) {
if (error) throw error;
iconSvg = xml.getElementsByTagName("svg")[0];
sel = d3.select(iconSvg).selectAll("#icon2");
var newSvg = d3.select("body").append("svg")
.attr({"id":"newicon", "width":50, "height":50
}).append(sel);
});
</script>
The "icons.svg" file content is following:
<?xml version="1.0" encoding="utf-8"?>
<svg width="100%" version="1.1" id="icons" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
<g id="icon1" width="16" height="16" >
<circle cx="7.5" cy="7.5" r="6.0" stroke="black" stroke-width="1" fill="#00AD5A" />
</g>
<g id="icon2" width="16" height="16" >
<circle cx="7.5" cy="7.5" r="6.0" stroke="black" stroke-width="1" fill="#FF0000" />
</g>
<g id="icon3" width="16" height="16" >
<circle cx="7.5" cy="7.5" r="6.0" stroke="black" stroke-width="1" fill="#FF00FF" />
</g>
</svg>
I'd be thankful for any help!
I found one solution to the problem. I changed lines
sel = d3.select(iconSvg).selectAll("#icon2");
to this:
sel = d3.select(iconSvg).select("#icon2").html();
and
var newSvg = d3.select("body").append("svg")
.attr({"id":"newicon", "width":50, "height":50
}).append(sel);
to this:
var newSvg = d3.select("body").append("svg")
.attr({"id":"newicon", "width":50, "height":50
}).append("g").html(sel);
and the end result is what I want.

SVG Path Overlay and Animate Out Another Path

I have an SVG of a dashed gray line. What I want to do is overlay that on top of a green SVG dashed line, and animate out the gray to reveal the green. Sorta like a meter moving from right to left.
I saw this example of how to make a dash line:
http://jsfiddle.net/ehan4/2/
and was able to do it but my line is already dashed.
I ended up doing this:
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 666.9 123.8" enable-background="new 0 0 666.9 123.8" xml:space="preserve">
<path opacity="0.4" stroke-width="3" fill="none" stroke="#66CD00" stroke-linecap="round" stroke-miterlimit="5" stroke-dasharray="1,6" d="
M656.2,118.5c0,0-320.4-251-645.9-0.7" />
<path id="top" opacity="0.4" fill="none" stroke="#AEAEAE" stroke-linecap="round" stroke-miterlimit="5" stroke-dasharray="1,6" d="
M656.2,118.5c0,0-320.4-251-645.9-0.7"/>
</svg>
var path = document.querySelector('#top');
var length = path.getTotalLength();
// Clear any previous transition
path.style.transition = path.style.WebkitTransition =
'none';
// Set up the starting positions
path.style.strokeDasharray = 1 + ' ' + 6;
path.style.strokeDashoffset = length;
// Trigger a layout so styles are calculated & the browser
// picks up the starting position before animating
path.getBoundingClientRect();
// Define our transition
path.style.transition = path.style.WebkitTransition =
'stroke-dashoffset 20s linear';
// Go!
path.style.strokeDashoffset = '0';
https://jsfiddle.net/ps5yLyab/
How can I overlay the two dash lines and animate out the gray?
You can do it with a clip path.
First we add a clipPath to the SVG.
<defs>
<clipPath id="myclip">
<rect id="cliprect" x="100%" y="0%" width="100%" height="100%"/>
</clipPath>
</defs>
This clip path is sized the same size as the SVG (width and height 100%) and starts with its x postion at the far right of the SVG (100%). So at the start it is not revealing anything.
Then every 10mS we reduce it's x coord by 1% (ie 100% -> 99% -> 98% etc). until it reached zero.
var cliprect = document.getElementById("cliprect");
var offsetX = 100;
var speed = 10;
function clipAdjust()
{
cliprect.setAttribute("x", offsetX+"%");
offsetX -= 1;
if (offsetX >= 0) {
window.setTimeout(clipAdjust, speed);
}
}
window.setTimeout(clipAdjust, speed);
Working demo below:
var path = document.querySelector('#top');
var length = path.getTotalLength();
// Clear any previous transition
path.style.transition = path.style.WebkitTransition =
'none';
// Set up the starting positions
path.style.strokeDasharray = 1 + ' ' + 6;
path.style.strokeDashoffset = length;
// Trigger a layout so styles are calculated & the browser
// picks up the starting position before animating
path.getBoundingClientRect();
// Define our transition
path.style.transition = path.style.WebkitTransition =
'stroke-dashoffset 20s linear';
// Go!
path.style.strokeDashoffset = '0';
var cliprect = document.getElementById("cliprect");
var offsetX = 100;
var speed = 10;
function clipAdjust()
{
cliprect.setAttribute("x", offsetX+"%");
offsetX -= 1;
if (offsetX >= 0) {
window.setTimeout(clipAdjust, speed);
}
}
window.setTimeout(clipAdjust, speed);
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 666.9 123.8" enable-background="new 0 0 666.9 123.8" xml:space="preserve">
<defs>
<clipPath id="myclip">
<rect id="cliprect" x="100%" y="0%" width="100%" height="100%"/>
</clipPath>
</defs>
<path opacity="0.4" fill="none" stroke="#AEAEAE" stroke-linecap="round"
stroke-miterlimit="5" stroke-dasharray="1,6" stroke-width="2"
d="M656.2,118.5c0,0-320.4-251-645.9-0.7"/>
<g clip-path="url(#myclip)">
<path stroke-width="3" fill="none" stroke="white"
stroke-linecap="round" stroke-miterlimit="5"
d="M656.2,118.5c0,0-320.4-251-645.9-0.7" />
<path id="top" opacity="0.4" stroke-width="3" fill="none" stroke="#66CD00"
stroke-linecap="round" stroke-miterlimit="5" stroke-dasharray="6,6"
d="M656.2,118.5c0,0-320.4-251-645.9-0.7" />
</g>
</svg>

Categories

Resources