Using javascript to change an embedded SVG in IE9 - javascript

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.

Related

Why is SVG textPath not working in Firefox when rendered by React

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.

Animate SVG path from <use> tag

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');

SVG childNodes return text

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.

Can't read SVG element contents in mobile Safari with Javascript

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.

Repaint issues in WebKit browsers when positioning SVG elements with JavaScript events

I have a custom SVG control I've created which consists of a time line representing 32 hours (24 hours from the current day plus 8 hours from the previous day). There's two sliders overlayed on the time line which can be dragged around to set the starting time and ending time, between which data is shown else where in the web app.
The SVG and JavaScript are both pretty lengthy and complicated, so I'll just post the basic structure for now. I'll post more details if requested. The SVG contains nested SVG elements and gets a little crazy, but it looks something like this:
<svg width="100%" height="100%" shape-rendering="crispEdges">
<svg id="TimeLineAdjust" x="1.5%" width="97%" overflow="visible">
"Some static stuff that draws out the time line..."
</svg>
<svg id="TimesContainer" x="1.5%" y="0" width="97%" height="100%" shape-rendering="geometricPrecision">
"Various labels and buttons and such..."
<text id="StartLabel"></text>
<text id="EndLabel"></text>
<text id="DurationLabel"></text>
</svg>
<svg id="TimeSlider" y="0" height="100%" overflow="visible">
<defs>
<polygon id="DefMarker" points="-0.5,77.5 -0.5,23.5 9.5,23.5 9.5,32.5 0.5,41.5 0.5,77.5" style="fill:rgb(0,0,255);stroke:rgb(0,0,255);stroke-width:1" />
</defs>
<rect id="SliderRect" x="0%" y="41.5" width="100%" height="36" style="stroke:none;opacity:0.5;fill:rgb(0,0,255)" />
<use id="SliderStartLine" x="0%" transform="scale(-1,1)" xlink:href="#DefMarker" />
<use id="SliderEndLine" x="100%" xlink:href="#DefMarker" />
</svg>
</svg>
Now when you mousedown on either "SliderRect", "SliderStartLine", or "SliderEndLine" I hook onto the mousemove event and use it to determine what to set the start time, end time, and duration variables else where in my JavaScript. Then I call another function which positions "TimeSlider" accordingly. It looks like this:
TimeSlider.setAttribute('x', newXVal);
TimeSlider.setAttribute('width', newWidthVal);
StartLabel.textContent = newStartVal;
EndLabel.textContent = newEndVal;
DurationLabel.textContent = newDurationVal;
This all works perfectly, so don't worry about those values that I'm assigning, they're correct. The only problem is that as the new positions are painted onto the screen, the old ones are "ghosting" across the screen, and they stay there until the mouseup event is fired. Also, SliderRect does not rescale to 100% of TimeSlider when it's width is changed. I was able to fix this issue with:
SliderRect.setAttribute('width', '100%');
But this shouldn't be necessary. Both of these issues only happen on Chrome/Win7 and Safari/Win7. It does not happen in IE9/FF9/Opera.
Any ideas?
Edit: Also, I've noticed that there's no ghosting when dragging "SliderEndLine". Only when dragging "SliderRect" or "SliderStartLine", which would lead one to believe that the problem must be with "TimeSlider.setAttribute('x', newXVal);"

Categories

Resources