To reuse certain SVG objects I have symbols defined in an SVG element which is written out at the top of my DOM. To display an SVG I can do:
<svg><use xlink:href="#symbol-identifier" /></svg>
To animate some SVG's I use Snap.svg, but I can't seem to create a Snap object from an SVG referenced by an xlink. To work around this I want to inject the SVG contents (already present in the DOM) at runtime into the SVG currently using the xlink.
To do this I need to get the contents (innerHTML?) of a symbol. This works in every browser i tested, except for mobile safari.
Below a simple test setup (without the symbol-wrap, but works the same):
HTML:
<div id="outer" style="width: 100px; height: 100px;">
<svg id="inner" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg">
<circle cx="10" cy="10" r="5" fill="#ffffff" />
</svg>
</div>
Javascript:
var outerElement = document.getElementById('outer');
var innerElement = document.getElementById('inner');
outerElement.innerHTML // returns <svg id=".. etc..
innerElement.toString() // returns [object SVGSymbolElement]
innerElement.getAttribute('viewBox') // returns 0 0 100 100
innerElement.innerHTML // returns undefined (in Mobile Safari)
// Everywhere else the content <circle .. etc .. is returned.
Why is this? And is there another way of getting the SVG contents as a string, without regexping away all the unwanted HTML around the outerElement.
XMLSerializer can serialize elements.
var string = XMLSerializer().serializeToString(innerElement);
should work for you.
Related
I'm trying to render a react component with an inline SVG element that has a text along a path. This is what is returned from the render method:
<div className="textsvg">
<svg width="100%" height="100%" viewBox="-50 -50 100 100">
<defs>
<path id="textPathTop" d={`
M 0 40
A 40,40 0 0 1 0,-40
A 40,40 0 0 1 0,40`}></path>
<path id="textPathBottom" d={`
M 0 -41.8
A 41.8,41.8 0 0 0 0,41.8
A 41.8,41.8 0 0 0 0,-41.8`}></path>
</defs>
<use xlinkHref="#textPathBottom" fill="none" stroke="red"></use>
<text fill="red" fontSize="4.5"><textPath xlinkHref="#textPathBottom">We go up, then we go down, then up again</textPath></text>
</svg>
</div>
This shows the "We go up, then we go down, then up again" text, but just in a straight horizontal line starting from 0,0.
Copying the resulting html into a codepen shows the result as it should look, using the textPath.
Why is the textPath ignored when rendered with ReactJS?
Using React 15.3.1 and checking in FF 52.0.2(32bit)
Already tried using _dangerouslySetInnerHTML for textPath, but that didn't work either.
Check if you have a <base href="..."> tag in your <head> element.
If so, Firefox won't be able to display your text, while Chrome will.
Firefox is searching for your xlink:href attribute at the base href url, it does not find it, so the text is just ignored.
A workaround is to use an absolute url :
<textPath xlink:href="http://pathtoyourpage#textPathBottom">
It is easier if you generate the svg with javascript :
textPath.setAttribute('xlink:href', `${location.href}#textPathBottom`)
Some similar weird behavior happen with mask and filter attributes.
I'm trying to animate an svg path using this technique by Jake Archibald
The technique works well when the svg code is pasted in my document, but fails when I'm referencing the svg from my "defs.svg" file.
My defs.svg looks like this:
<svg xmlns="http://www.w3.org/2000/svg">
<symbol viewBox="0 0 48 50.6" id="icon-result" fill="#FFF">
<path d="M19 49.4c-2 1.7-5.1 1.5-6.9-.5l-11-12.8c-1.7-2-1.5-5.1.5-6.9 2-1.7 5.1-1.5 6.9.5l11 12.8c1.8 2.1 1.6 5.2-.5 6.9z"/>
<path d="M13.6 49.4c-2.2-1.5-2.9-4.5-1.4-6.8L39 2.2C40.5 0 43.5-.7 45.8.8c2.2 1.5 2.9 4.5 1.4 6.8L20.3 48.1c-1.4 2.2-4.5 2.8-6.7 1.3z"/>
</symbol>
</svg>
In my code I do the following:
<svg class="Dashboard__nav-icon">
<use xlink:href="/svg/defs.svg#icon-result" />
</svg>
When trying to animate the code this way I get null when trying to get the path by using Jake Archibalds technique linked above. Is there a way to get the path while still using a separate defs.svg file and the <use> tag?
You won't be able to get the DOM path element via the second <svg> element. That is because elements referenced via a <use> do not appear in the DOM tree where they are used. So the following will not work:
var path = document.querySelector('.Dashboard__nav-icon path');
The symbols elements are not visible in the main document's DOM tree.
However you would be able to reference the paths directly via their definition. But for that to work you would need to inline the defs.svg file.
So, if the defs.svg is in the same file, then you would be able to use:
var path = document.querySelector('#icon-result path');
I have an inline SVG and try to loop over all paths within a group element. When using childNodes I found out that Browsers add an extra text child for every path. I am curious why browsers are doing so and if there is a smart way to just loop over real child elements.
I've created a little JSBin to demonstrate the behaviour: http://jsbin.com/tutisakege/1/edit?html,css,js,console,output
(Check the output of the console)
HTML
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 100 100">
<g id="test">
<path d="M50 0 L0 100 L100 100 Z" />
<path class="red" d="M25 0 L25 25 L75 0 L75 25 Z" />
</g>
</svg>
JS
var group = document.querySelector('#test'),
children = group.childNodes;
The childrenobject now holds 5 entries even though my test group only has 2 paths.
Note: I know I could loop over all entries and check whether I have an instance of SVGPathElement but that seems a but cumbersome to me.
(I've tested it in the latest Chrome and Firefox)
Browsers didn't add it, they are right there in the document source. There is whitespace between the path elements i.e. carriage returns and spaces.
You can skip the text by using element.children but that apparently only works on Firefox and Chrome so if you want it done portably you'll probably need to stick with checking for element instances as you suggest in the question.
I am trying to use basic DOM manipulation with Javascript to change the contents of a text-node in an SVG.
Basically this is the SVG I am embedding:
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 200 200">
<text>
<tspan x="100" y="100" fill="#000000" font-size="10">Name:
<tspan id="firstName">placeholder</tspan>
</tspan>
</text>
</svg>
And this is my javascript snippet:
document.querySelector('#firstName').innerHTML = 'Test';
This works in Chrome. It fails in IE9.
I have tried fetching the SVG as a string with AJAX, creating a JQuery element from said string and doing the replacement before adding it to the DOM but that did not work either. IE9 did manage to render it with the placeholder text though, so I suppose I can fall back on using a regexp on the string before creating a DOM element from it, but I'd love to avoid that.
Interestingly enough, I can delete the tspan element and IE will remove it. I can also edit the contents of the tspan via IE's developer tools and the image gets updated.
I just can't seem to use either innerHTML or JQuery.html() to update it.
I've created a jsfiddle here that tries to change an already embedded DOM since that was an easier thing to turn into a fiddle than the JQuery approach. The behaviour is the same so I am guessing the underlying problem is the same as well.
If anyone can explain to me why this does not work in IE9 that would be great.
SVG is not HTML, that's why innerHTML doesn't work. Use textContent instead:
var text = document.querySelector('#firstName').textContent = 'Test';
Here is the workaround, basically manipulation the SVG as a string prior to injecting it into the DOM.
var svgStr = ''
+ '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 200 200">'
+ ' <text>'
+ ' <tspan x="100" y="100" fill="#000000" font-size="10">Name: ##PLACEHOLDER##</tspan>'
+ ' <text>'
+ '</svg>';
svgStr = svgStr.replace('##PLACEHOLDER##', 'Test');
$('body').append(svgStr);
(I'm obviously not inlining the SVG in my code, but fetching it as a resource with an AJAX request, however that is kind of irrelevant here so it's omitted).
This works in IE9, as demonstrated here.
Any kind of cleaner solution is welcome.
Consider this simple SVG SMIL animation:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-50 -50 100 100">
<circle r="40" fill="red">
<animate
attributeType="CSS" begin="click"
attributeName="fill" to="blue" dur="0.3s" fill="freeze"/>
</circle>
</svg>
This works correctly in Chrome v18 on Windows (modulo a bug with holding the color):
http://phrogz.net/svg/change-color-on-click-simple.svg
When I generate the <animate> element using JavaScript, all works well in Firefox, Safari, and Opera, but not Chrome. In Chrome, nothing happens when I click on the circle.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-50 -50 100 100">
<circle r="40" fill="red"/>
<script>
var c = document.querySelector('circle');
createOn(c,'animate',{
attributeType:'CSS', begin:'click',
attributeName:'fill', to:'blue',
dur:'0.3s', fill:'freeze'
});
function createOn(el,name,attrs){
var e = el.appendChild(document.createElementNS(el.namespaceURI,name));
for (var name in attrs) e.setAttribute(name,attrs[name]);
return e;
}
</script>
See this JavaScript version here:
http://phrogz.net/svg/change-color-on-click-simple-js.svg
There are no script errors in the console. The content of the first example was actually generated by choosing Copy As HTML from the Chrome Developer Tools after loading the second example, so I know that it is producing the correct attribute names and values. The namespaceURI of the <animate> element is the same between both (the SVG namespace), as is the namespaceURI of all attributes (null).
Is there a way to get JS-generated <animate> elements to work in Chrome?
If I define the attributes before appending the animation, it appears to work.
http://jsfiddle.net/VFUHk/