Accessing a DOM object defined in an external SVG file - javascript

SVG standard allows to use and refer external SVG files.
I have a file circle.svg that defines a circle object with id "the_circle".
From the main SVG file I am able to include this circle and animate it, using SVG linking.
I would also like to access the same circle object via javascript, how can I do this ?
What is the javascript equivalent of xlink:href="url(#the_image)#the_circle" ?
Using document.getElementById('the_image') I can only access the SVGImageElement but not the objects defined inside the included SVG.
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" >
<image
id="the_image"
x="0" y="0" width="100%" height="100%"
xlink:href="circle.svg" />
<animateTransform
xlink:href="url(#the_image)#the_circle"
attributeName="transform" attributeType="XML"
type="translate"
from="0" to="25"
dur="1s" repeatCount="indefinite"
additive="replace" fill="freeze" />
</svg>

It seems like the "right" way to do this would actually be to use an SVG "use" element, rather than an image. The reason for this is that the DOM interface of the SVG use element specifies a property "instanceRoot", which allows you to get the root of the "instance tree" corresponding to that use element: http://www.w3.org/TR/SVG/struct.html#InterfaceSVGUseElement
So, you would end up with a solution that looks something like the following:
circle.svg:
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="4in" height="4in" id="the_svg"
viewBox="0 0 4 4" version="1.1"
xmlns="http://www.w3.org/2000/svg">
<circle r="1" fill="blue" stroke="none" id="the_circle"/>
</svg>
Document which uses the svg root node of circle.svg:
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" id="foo"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<use xlink:href="circle.svg#the_svg"/>
</svg>
Unfortunately, though, while Firefox supports use of the use element with external documents, there's currently a bug in Webkit which does not allow this: https://bugs.webkit.org/show_bug.cgi?id=12499
Also, Firefox does not seem to implement the instanceRoot property for use elements.
So, it seems you may need to work around the limitations of current SVG implementations. The way I would recommend doing this is to use XMLHttpRequest to download the document to which you would like to link, and import the DOM of the downloaded document into your host document's DOM. The following code implements this, and works in Firefox, Opera and Chromium:
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" id="foo"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<script>
function fetchXML (url, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onreadystatechange = function (evt) {
//Do not explicitly handle errors, those should be
//visible via console output in the browser.
if (xhr.readyState === 4) {
callback(xhr.responseXML);
}
};
xhr.send(null);
};
//fetch the document
fetchXML("http://localhost:8082/tmp/circle.svg",function(newSVGDoc){
//import it into the current DOM
var n = document.importNode(newSVGDoc.documentElement,true);
document.documentElement.appendChild(n);
var circle = document.getElementById("the_circle"); //now you have the circle
})
</script>
</svg>

You can access the necessary element a bit easier:
document.getElementById('the_image').contentDocument.getElementById('the_circle')
See this image for reference (taken on dev.opera.com)

To supplement #echo-flow's excellent solution with the code in jQuery/Coffeescript:
$.get '/assets/hexagon.svg', (svgFileData)->
svgTag = svgFileData.documentElement
$('body').append(svgTag)
circle = $('#the_circle')

Here's a solution to this problem when using React and ES6. Usage:
<SvgImage url='pathToImage.svg'></SvgImage>
https://gist.github.com/mikkel/8b79a713ff06bbec379d

Related

Access global document from shadow DOM script

I have the following svg. Of course document.body is always null because it refers to shadow DOM. Is it possible to access global document from this script?
Please don't tell me it doesn't make sense, believe me it does.
<?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" xmlns="http://www.w3.org/2000/svg" width="266" height="100" viewBox="0 0 266 100"
overflow="visible" enable-background="new 0 0 266 100" xml:space="preserve">
<g><rect fill="#3B5998" width="266" height="100"/></g>
<g>
<path fill="#FFFFFF" d="M242.2011719,66.1777344c1.4726562,0,2.6464844,1.2011719,2.6464844,2.7011719
c0,1.5234375-1.1738281,2.7109375-2.6572266,2.7109375c-1.4755859,0-2.6728516-1.1875-2.6728516-2.7109375
c0-1.5,1.1972656-2.7011719,2.6728516-2.7011719H242.2011719z M242.1904297,66.5976562
c-1.1865234,0-2.1582031,1.0214844-2.1582031,2.28125c0,1.2832031,0.9716797,2.2910156,2.1689453,2.2910156
c1.1982422,0.0117188,2.1552734-1.0078125,2.1552734-2.2792969s-0.9570312-2.2929688-2.1552734-2.2929688H242.1904297z
M241.6865234,70.4511719h-0.4804688V67.4375c0.2519531-0.0351562,0.4921875-0.0703125,0.8515625-0.0703125
c0.4560547,0,0.7539062,0.0957031,0.9365234,0.2265625c0.1767578,0.1328125,0.2724609,0.3359375,0.2724609,0.6230469
c0,0.3984375-0.2617188,0.6367188-0.5849609,0.734375v0.0234375c0.2626953,0.0488281,0.4423828,0.2871094,0.5029297,0.7304688
c0.0703125,0.46875,0.1425781,0.6484375,0.1904297,0.7460938h-0.5029297
c-0.0712891-0.0976562-0.1435547-0.3730469-0.2041016-0.7695312c-0.0703125-0.3828125-0.2636719-0.5273438-0.6484375-0.5273438
h-0.3330078V70.4511719z M241.6865234,68.7832031h0.3476562c0.3935547,0,0.7285156-0.1445312,0.7285156-0.5175781
c0-0.2636719-0.1904297-0.5273438-0.7285156-0.5273438c-0.1572266,0-0.265625,0.0117188-0.3476562,0.0234375V68.7832031z"/>
</g>
<script>console.log(document.body)</script>
</svg>
Yes, you can access the global document from a script in your external SVG file.
Your access is via:
window.top.document.body

If I am embedding an SVG using an <object> tag, is it possible to use normal DOM methods in JS to access the children elements?

If I am embedding an SVG using an <object> tag, is it possible to use normal DOM methods to access the children elements?
Example
First a simple SVG:
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg"
viewBox="0,0 100, 100">
<defs>
<style>
#cls-1 {
fill: none;
stroke: #000;
stroke-width: 4;
}
</style>
</defs>
<title>Circles</title>
<g id="cls-1">
<circle cx="30" cy="30" r="10" />
<circle cx="60" cy="60" r="10" />
</g>
</svg>
Then an HTML file that embeds the SVG using and tries to access the group defined in the SVG within a script:
<html>
<body>
<object
type="image/svg+xml"
height="200px"
data="mysvg.svg"
></object>
<button onclick="myAnimate()" />
<script>
const myAnimate = () => {
const group = document.getElementById("cls-1");
};
</script>
</body>
</html>
Wy doesn't this work?
I see in the devtools that the first child of this is #document. So it seems to have it's own DOM, I think I read this, but still have not wrapped my head around how it works.
Is there a way to access the element with id "cls-1" from JS?
EDIT: one way to make this work is to add the script within the svg itself; the DOM methods work normally for the DOM in the SVG

I need help understanding how to get s stringified (variable) version of an SVG image

I am currently working with an svg image that I need help stringifying, If that makes sense. Basically how to make the image open in a text/code editor as only variables?
Here is an example of what I am looking for:
var svgData='PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MTIiIGhlaWdodD0iNTEyIiB2ZXJzaW9uPSIxLjEiPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0ic3Ryb2tl
Currently when open an svg image in a text editor I just get something like this.
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG
Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 255.1 227.3" style="enable-background:new 0 0 255.1
227.3;" xml:space="preserve">
<style type="text/css">
Any help is welcome! Thank You.
It looks like svgData is cut a little short. But if I understand your question right, you want that encoded svgData available in a textarea?
You can use the native functions btoa and atob (https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding) to encode and decode the svg content.
For example using your svgData value: atob("PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MTIiIGhlaWdodD0iNTEyIiB2ZXJzaW9uPSIxLjEiPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0ic3Ryb2tl") = <svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" version="1.1"><defs><linearGradient id="stroke"
and we can convert it back with:
btoa('<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" version="1.1"><defs><linearGradient id="stroke"') = "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MTIiIGhlaWdodD0iNTEyIiB2ZXJzaW9uPSIxLjEiPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0ic3Ryb2tlIg=="

Problem separating js out from an svg file

I have an svg file which is almost entirely made up of a script. I'd like to separate the script out, so that I can run it through a compressor, but I can't find a way to do this. Any help gratefully appreciated.
The svg file look like this:
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg id="chart"
xmlns="http://www.w3.org/2000/svg"
onload="init(evt)" >
<script type="application/ecmascript">
<![CDATA[
...lots of code
//]]>
</script>
</svg>
What I've done is extracted out "lots of code" as lotsOfCode.js, and changed the svg file to:
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg id="chart"
xmlns="http://www.w3.org/2000/svg"
onload="init(evt)" >
<script type="application/ecmascript" src="lotsOfCode.js">
</script>
</svg>
However, this doesn't work. The browser complains that it can't find the onload 'init' function. Any ideas? Do I have to do something to tell the browser that 'init' is in 'lotsOfCode.js'?
Thanks -
Al
Try using xlink:href instead of src:
<script type="text/ecmascript" xlink:href="lotsOfCode.js"></script>
Edit: You'll also need to reference the xlink namespace:
<svg id="chart"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
onload="init(evt)" >
In addition to Gillys answer, have you tried moving the script include to go before the svg line?
Like this:
<script type="application/ecmascript" src="lotsOfCode.js">
</script>
<svg id="chart"
xmlns="http://www.w3.org/2000/svg"
onload="init(evt)" >
</svg>
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg id="chart"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
onload="init(evt)" >
<script type="text/ecmascript" xlink:href="lotsOfCode.js"></script>
<circle id='BlueCircle' cx='25' cy='25' r='20' style='fill:blue; '/>
</svg>

How to access SVG elements with Javascript

I'm messing around with SVG and I was hoping I could create SVG files in Illustrator and access elements with Javascript.
Here's the SVG file Illustrator kicks out (It also seems to add a load of junk to the beginning of the file that I've removed)
<?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" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="276.843px" height="233.242px" viewBox="0 0 276.843 233.242" enable-background="new 0 0 276.843 233.242"
xml:space="preserve">
<path id="delta" fill="#231F20" d="M34.074,86.094L0,185.354l44.444,38.519l80.741-0.74l29.63-25.186l-26.667-37.037
c0,0-34.815-5.926-37.778-6.667s-13.333-28.889-13.333-28.889l7.407-18.519l31.111-2.963l5.926-21.481l-12.593-38.519l-43.704-5.185
L34.074,86.094z"/>
<path id="cargo" fill="#DFB800" d="M68.148,32.761l43.704,4.445l14.815,42.963l-7.407,26.667l-33.333,2.963l-4.444,14.074
l54.074-1.481l22.222,36.296l25.926-3.704l25.926-54.074c0,0-19.259-47.408-21.481-47.408s-31.852-0.741-31.852-0.741
l-19.259-39.259L92.593,8.316L68.148,32.761z"/>
<polygon id="beta" fill="#35FF1F" points="86.722,128.316 134.593,124.613 158.296,163.872 190.889,155.724 214.593,100.909
194.593,52.02 227.186,49.057 246.444,92.02 238.297,140.909 216.074,172.761 197.556,188.316 179.778,169.798 164.963,174.983
163.481,197.946 156.815,197.946 134.593,159.428 94.593,151.279 "/>
<path class="monkey" id="alpha" fill="#FD00FF" d="M96.315,4.354l42.963,5.185l18.519,42.222l71.852-8.148l20.74,46.667l-5.926,52.593
l-24.444,34.074l-25.185,15.555l-14.074-19.259l-8.889,2.964l-1.481,22.222l-14.074,2.963l-25.186,22.963l-74.074,4.444
l101.481,4.444c0,0,96.297-17.777,109.63-71.852S282.24,53.983,250.389,20.65S96.315,4.354,96.315,4.354z"/>
</svg>
As you can probably see, each element has an ID, and I was hoping to be able to access individual elements with Javascript so I could change the Fill attribute and respond to events such as click.
The HTML is bog basic
<!DOCTYPE html>
<html>
<head>
<title>SVG Illustrator Test</title>
</head>
<body>
<object data="alpha.svg" type="image/svg+xml" id="alphasvg" width="100%" height="100%"></object>
</body>
</html>
I guess this is two questions really.
Is it possible to do it this way, as opposed to using something like Raphael or jQuery SVG.
If it is possible, what's the technique?
UPDATE
At the moment, I've resorted to using Illustrator to create the SVG file, and I'm using Raphaƫl JS to create paths and simply copying the point data from the SVG file and pasting it into path() function. Creating complex paths such as might be needed for a map, by coding the point data manually is (to my knowledge) prohibitively complex.
Is it possible to do it this way, as opposed to using something like Raphael or jQuery SVG?
Definitely.
If it is possible, what's the technique?
This annotated code snippet works:
<!DOCTYPE html>
<html>
<head>
<title>SVG Illustrator Test</title>
</head>
<body>
<object data="alpha.svg" type="image/svg+xml"
id="alphasvg" width="100%" height="100%"></object>
<script>
var a = document.getElementById("alphasvg");
// It's important to add an load event listener to the object,
// as it will load the svg doc asynchronously
a.addEventListener("load",function(){
// get the inner DOM of alpha.svg
var svgDoc = a.contentDocument;
// get the inner element by id
var delta = svgDoc.getElementById("delta");
// add behaviour
delta.addEventListener("mousedown",function(){
alert('hello world!')
}, false);
}, false);
</script>
</body>
</html>
Note that a limitation of this technique is that it is restricted by the same-origin policy, so alpha.svg must be hosted on the same domain as the .html file, otherwise the inner DOM of the object will be inaccessible.
Important thing to run this HTML, you need host HTML file to web server like IIS, Tomcat
In case you use jQuery you need to wait for $(window).load, because the embedded SVG document might not be yet loaded at $(document).ready
$(window).load(function () {
//alert("Document loaded, including graphics and embedded documents (like SVG)");
var a = document.getElementById("alphasvg");
//get the inner DOM of alpha.svg
var svgDoc = a.contentDocument;
//get the inner element by id
var delta = svgDoc.getElementById("delta");
delta.addEventListener("mousedown", function(){ alert('hello world!')}, false);
});
If you are using an <img> tag for the SVG, then you cannot manipulate its contents (as far as I know).
As the accepted answer shows, using <object> is an option.
I needed this recently and used gulp-inject during my gulp build to inject the contents of an SVG file directly into the HTML document as an <svg> element, which is then very easy to work with using CSS selectors and querySelector/getElementBy*.

Categories