Using Javascript with SVG


26 Apr 2018 Code on Github

Introduction

[This is a completely rewritten version of a post from March 29, 2013]

Like HTML, SVGs are represented using the Document Object Model (DOM) and so can be manipulated with Javascript relatively easily, especially if you are familiar with using JS with HTML. All the code examples can be found by following the Github link at the top of this post.

Multiple methods

I'm going to cover how to work with two types of SVGs:

  • Inline
  • External (added using <embed>, <object> or <iframe> tag)

As mentioned it my basic SVG tutorial, it's also possible to add SVGs using the <img> tag and as a background-image in CSS, but neither of these methods allow you to manipulate the SVG with JS (or CSS). I'll also cover using JS

  • Inside the SVG itself
  • External to the SVG (within the HTML document or as a separate file altogether).

Most commonly, you'll probably want to use inline SVGs with external JS. This lets you to have separation of concerns, and easily reuse the JS for multiple SVGs on a website. However, you might already have the SVGs files, perhaps from Inkscape or Illustrator, in which case it can be useful to embed them. I sometimes like to have the JS embedded into external SVG files, so all the code is wrapped up together and can be emailed as a single file so the recipient can just open the SVG in their browser and have it work.

Getting the SVG document

If you using an external SVG or you want to create a new element within a SVG, then you will first need to get the SVG document. If you are just getting or setting the attributes of an inline SVG, then you can skip this section.

Below are two SVGs, one inline and one external, added with an <object> tag. The inline SVG has an id of "inline-1", the external SVG has an id of "external-1", and the object has an id of "svg-object".

<object id="svg-object" data="path/to/external.svg" type="image/svg+xml"></object>
Inline SVG

Note that they look different because the inline SVG is styled by the CSS from this page. Also, if you right click on the external SVG you should have an additional option to view its source.

Inline SVG

To get an inline SVG element, you can use document.getElementById() regardless of whether the script tag is inside the SVG or not (and you might as well separate it from the SVG).

<script type="text/javascript">
    var svg = document.getElementById('inline-1');
    console.log(svg);
</script>

External SVG + Internal JS

For an external SVG, you can use the same code when adding the <script> element into the SVG itself. However, you may want to wrap the code with CDATA. If you don't, then the XML parse will consider the JS code part of XML, and if you use < or >, it will break (as in this example), thinking you're trying to start or end a tag.

This code will work despite containing 1 < 2, because of the <![CDATA[ ... ]]> wrapping.

<script type="text/javascript"><![CDATA[
    var svg = document.getElementById('external-1');
    console.log(svg);
    console.log(1 < 2);
]]></script>

In this case we could also use:

var svg = document.getElementsByTagName('svg')[0];

We can do this because the SVG cannot access the HTML document it's embedded into and so cannot "see" the other SVGs on the page.

External SVG + External JS

In this case we can't access the SVG element directly as it's hidden inside the <object> element. So first, we have to get the object and then access its contentDocument. Once we have the SVG document, we can continue as before.

However, there is also an issue that the SVG within the object might not have loaded by the time we reach the script element (which I'm assuming you've added to the end of the HTML document). So we have to make sure we don't try to get the element before the HTML window has loaded.

You would also need to do this if you we using an inline SVG and either had the <script> element before it.

<script type="text/javascript">
  window.addEventListener("load", function() {
    var svgObject = document.getElementById('svg-object').contentDocument;
    var svg = svgObject.getElementById('external-1');
    console.log(svg);
  });
</script>

Getting an element

Now we know how to get the SVG document, let's get an element from inside the SVG with an id of "item".

To get an element from an inline SVG, you can use:

var element = document.getElementById('item');

To get an element from an external SVG, first get the SVG object as before, and then get the element using the SVG object's getElementById() method:

window.addEventListener("load", function() {
  var svgObject = document.getElementById('svg-object').contentDocument;
  var element = svgObject.getElementById('item');
});

You can also use the getElementsByTagName() or getElementsByClassName() methods, but remember these will return collections of items, not just one.

Getting and setting attributes

Once you have selected an element, you can get and set its attributes with getAttributeNS() and setAttributeNS(). For example, we can get an element's y coordinate and add 10 to it like so:

var y = parseFloat(element.getAttributeNS(null, 'y'));
element.setAttributeNS(null, 'y', y + 10);

This will work regardless how the SVG is added to the page or where the JS is.

Note that these functions are slightly different from the standard getAttribute and setAttribute methods because the elements are not HTML elements, rather in the SVG namespace (NS stands for namespace). All this means is that you have to pass an additional parameter to each method, which can just be null.

Inline SVG

Now the text elements have been moved down. Something else to note is that both the text elements have an id of "item", which works because they are not in the same document.

Creating and removing elements

To create elements, you need to use the relevant document's createElementNS() method, passing in the SVG namespace and the tag name. You then will most likely want to add it to the SVG element with appendChild(). You can also use the insertBefore() or insertAfter() methods of an SVG element.

Whatever method you use, so long as you have got the svg element, you can use:

var element = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
svg.appendChild(element);
Inline SVG

Creating text

Creating a text element that contains text is the same as it is in HTML: you have to create a separate text node using createTextNode(). You then append this to a text element. For example:

var element = document.createElementNS('http://www.w3.org/2000/svg', 'text');
element.setAttributeNS(null, 'x', 5);
element.setAttributeNS(null, 'y', 15);
var txt = document.createTextNode("Hello World");
element.appendChild(txt);
svg.appendChild(element);

Removing an element

Removing an element is the same as it is in HTML. Get the element by id (or however), and then pass it into:

var element = document.getElementById('item');
svg.removeChild(element);

Simple example

This is a simple example, using sliders to change the cx and cy attributes of a circle element.

var moveSlider = function(slider, direction) {
    var value = slider.value;
    var circle = document.getElementById("target");
    var coord = "c" + direction;
    circle.setAttributeNS(null, coord, value * 5);
}
:
:

This function is called whenever the sliders are changed with the oninput event:

<input type="range" min="1" max="60" value="20" oninput="moveSlider(this, 'x')"/>
 <input type="range" min="1" max="40" value="10" oninput="moveSlider(this, 'y')"/>

Comments (31)

Richard Smith on 17 Aug 2013, 12:11 p.m.

You should check out D3.js, which is the canonical way to manupulate SVG with JS.

Peter on 18 Aug 2013, 2:15 p.m.

Thanks Richard. I have looked into D3.js a bit and it is quite cool for graphs, but I find it can be a bit limiting.

Anonymous on 28 Nov 2013, 7:26 p.m.

Brilliant simple. I've searching hours on the internet before I've found your exemple that enlightened me. Thank you very much!

Sorin on 28 Nov 2013, 10:37 p.m.

Hi,

When I put the variable outside the functions but into JavaScript, the script doesn't work. It seems that it doesn't like the global variables. What is to do?

For example, the following script doesn't work

<script type='text/javascript'>
var svg = document.getElementById("circle-svg");
var svgDoc = svg.contentDocument;

var moveSlider = function(slider, direction) {
var circle1 = svgDoc.getElementById("my-circle");
var value = slider.value;
circle1.setAttributeNS(null, "c" + direction, value * 5);
}

But that script works:

<script type='text/javascript'>
var moveSlider = function(slider, direction) {
var svg = document.getElementById("circle-svg");
var svgDoc = svg.contentDocument;
var circle1 = svgDoc.getElementById("my-circle");

var value = slider.value;
circle1.setAttributeNS(null, "c" + direction, value * 5);
}

Why's that?

Serge on 15 Apr 2014, 4:37 p.m.

My way: http://svgmnemo.ru/pub/svgdyn.html, but on the Russian, sorry.

Adam on 6 Sep 2018, 1:13 p.m.

I'd like like to render an SVG of my office floor plan and then allow users to search for a person using search bar or drop down menu then have the persons desk change colour to show where they sit. is this possible with Javascript? I'm a bit of a novice at the minute with big idea's ;o)

Peter on 6 Sep 2018, 9:38 p.m.

Yep, that's definitely possible. Give all the desks an id, then select by that id when the dropdown is chosen, then add a class to that element to highlight it. You'll also have to remove the highlight when you select a different desk. I might be able to create a prototype this weekend if I get time.

Mikey on 21 Sep 2018, 1:52 p.m.

Peter, thank you for these tutorials. Really, really helpful because they are clear and cover so much.

sam on 22 Oct 2018, 8:32 a.m.

Hi Peter...How can we change the text of the SVG text element with the data from our SQL database.I have a <text> tag inside <g> tag.I want to change its value dynamically from the database

Peter on 27 Oct 2018, 3:15 p.m.

I assume you know how to get the value from your database (using AJAX or whatever). Then give the text element an id so you can use var el = document.getElementById('some-id');. Then the key code you need is el.textContent = "New text content";. I'll update the tutorial to include this, thanks.

Suresh Talari on 2 Nov 2018, 7:42 a.m.

Hello Peter,
First of all, a fantastic tutorial.

I have a question. I have an SVG file has <g> tags with Ids which are system defined and empty <title> tags. and this SVG file is an export from a visio drawing.
The requirement is to assign a <title > to enable tooltip feature on the .svg file. So , is it possible that i can write a write a javascript based macro on visio to assign the id for <g> tag?. any references might help.
and also is it possibel to assign < title> with a value based on the id? based on the tutorial i assume we can do it by calling an external javascript.

I tried to explain the situation at its best. Please drop me an email at [email protected] if you feel the responses create a noise on the comments section here.

Thanks in advance

Isaac on 9 Nov 2018, 12:48 a.m.

Hi Peter,

Cannot thank you enough for the great examples and easy to follow explanations you share in these tutorials. This is really neat and appreciate your generosity by showing the know how of your knowledge in this forum.

I used your to drag and drop tutorial to make svg elements draggable in a blueprint. Now I am experimenting to develop an small library to enable svg geometries to make them snappable on top/inside others svg elements. Any clue you may have pointing me to the right direction would be greatly appreciated!

Thanks so much!

Peter on 15 Nov 2018, 2:35 p.m.

Thanks Isaac,
Your program sounds interesting. I'm not 100% sure how you want it to work. I guess you'd have to have something that tests for when objects overlap., which will probably require an array of elements. Rect-rect intersections are pretty easy, and snapping to an edge is pretty easy, you just make the values equal. You can email me via the link on the homepage if you want to discuss it further.

Mukesh on 23 Nov 2018, 8:49 a.m.

I am not able to get click event when svg image is inside the object... and script is not inside the svg.
i want click event on the object

Jeremy on 4 Jan 2019, 9:50 p.m.

Isaac,
Are you familiar with Inkscape, the open-source, native SVG editing system?

If not, check it out. It has SO MANY snapping options to join paths , flexible snap distances for with 'weighting' options to prioritize certain types of snaps over others, and several ways to combine, split, shape paths based on overlaps and lack thereof (ex. union, difference, intersection, exclusion, interpolation).

The fact it also has snappsble guides that can be rotated to any 2D angle is almost unfair, as it points out how miserable life has been without them.

If your main point is to design and write code, that's great too, though I would still suggest using Inkscape, Illustrator, or something similar as a model of workflows and geometries, and let the things you like (or don't like) guide your development choices.

Bill on 16 Jan 2019, 7:11 p.m.

Thanks for a very nice tutorial.

I'm struggling with adding a 'symbol' element to the SVG object in the DOM then modifying a node to refer to that new 'symbol'. When I'm done, the DOM looks fine, but the object doesn't re-render.

However, I can modify the node successfully to refer to a 'symbol' that was originally part of the SVG object, and it works fine. Seems like the newly created 'symbol' element isn't getting registered by the SVG object for some reason.

Any ideas?

John on 26 Mar 2019, 3:08 p.m.

Thanks for a great article, I am trying to set the values within an SVG element.

You are likely familiar with the Gartner Hype Cycle. Many years ago I had a nice animated version covering a number of years animated in powerpoint. However..I would like to be able to convert that into html/SVG

Key for it working is for each of the animated elements along the path to be able to travel at a different speed. One of which is below.

<animateMotion xlink:href="#c3"
begin="startButton.click"
dur="30s"
calcMode="discrete"
keyPoints="0.0;0.1;0.2;0.3;0.4;0.5;0.6;0.7;0.8;0.9"
keyTimes="0;0.19;0.36;0.51;0.64;0.75;0.84;0.91;0.96;0.99"
repeatDur="indefinite">
<mpath xlink:href="#pfad" />
</animateMotion>

I want to pass a paired value of keypoints and keytimes from a JSON file, however.....I want to drive the motion from a slider. Can you advise on a method to pick the values from a JSON based on the position of the slider as it moves from 1995-----to 2018

Any advice on if these value can be set in this fashion? any advice on how to do it.

Thanks

John

TheGeek on 30 May 2019, 7:44 p.m.

how could you change svg elements from an included file that is gotten using
background-image: url("file-name.svg");

Alex on 7 Jul 2019, 7:58 a.m.

Wow, thank you so much. That tutorial really helped, as I found snap svg a little bit confusing at the beginning and for simple stuff your way is a lot easier :-)

Nikita Beznosikov on 9 Jul 2019, 7:19 p.m.

Hey! I have some question.
I need to call external .js file from different server.

I tried to do it like this
.picture.sgv file

<svg width="100%" height="100%" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<script type="text/javascript">
// <![CDATA[
(function () {
var head = window.parent.document.head;
var script = document.createElement('script');
script.setAttribute('type', 'text/javascript');
script.setAttribute('src', 'https://domain.com/blog/assets/j/test.js');
head.appendChild(script);
}());
// ]]>
</script>

<circle cx="50" cy="50" r="45" fill="green" />
</svg>

so it adds <script> to head but doesn't call it.

in .html file I used

<object data="./picture.svg" type="image/svg+xml">
</object>

do you know how to id ?

SusanSusan on 16 Jul 2019, 1:16 p.m.

Where is the HTML code (not JS) for adding an svg as an internal... or external???
I don't see anything marked internal-1 or external-1 in html.

AlaricZ on 26 Oct 2019, 11:10 p.m.

Great article! I'm using it as a cheat sheet of manipulating svg with js. P.S. I don't think there is a "canonical' way of using svg, or any technology. Whatever works works.

sgreen on 7 Nov 2019, 7:17 a.m.

Thanks for this excellent post.
document.getElementById('svg-object').contentDocument return null in section External SVG + External JS when i set the <object> data prop with 'https://rawgit.com/petercollingridge/code-for-blog/master/svg-interaction/svg-and-js/external1.svg'.
I host with npm package http-server and external1.svg can show up but contentDocument is null, it seem to be cors
after i check these post
https://stackoverflow.com/questions/43081025/why-does-contentdocument-returns-null
http://jsfiddle.net/8kf36L0j/16/
https://jsfiddle.net/Lfhkxkz6/
but i wonder what's really going on with object's data request since https://rawgit.com/petercollingridge/code-for-blog/master/svg-interaction/svg-and-js/external1.svg response with the http header access-control-allow-origin: *

Adrian on 26 Nov 2019, 7:37 p.m.

Hi Peter,

Your draggable tutorials are amazing!

I have an external SVG that I have displayed within an Object tag on my website and it works great on desktop, but it isn't working on mobile or tablet despite following your instructions about making the SVG responsive. Is there anything additional that needs to be done for this?

Ron on 20 Jan 2020, 10:28 p.m.

I'm currently using $("polygon, path").hover(function(e) {} on inline SVG code.

I'm looking to call same on hove on svg elements <object> embedded.

IS this possible and what is the syntax?

I.E.
$(svg.contentDocument.getElementById('embeddedSVG').path).hover(function(e) {}

Thanks in advance, Ron

Subbu on 28 Apr 2020, 11:05 a.m.

That is a wonderful tutorial Peter. Can you give some direction on how to import already existing svg files (using drag and drop may be) and combine them into a single svg on a web page, that can be used later on another web page as a single svg image.

djw on 14 May 2020, 7:06 p.m.

It seems that multiple instances of the same inline SVG (with different id's) can cause problems with calls to:
document.getElementsByClassName('foo')
- it returns all matches in _all_ the SVGs (Chrome Version 58.0.3029.110).

A workaround is to use:
var s = document.getElementById('mySVG'), // ie. get top level SVG element
es = s.getElementsByTagName('circle')
and then filter es by className, or other criteria.

Conclusion: inline JS _can_ see its neighbours.

Gordon Moore on 23 May 2020, 10:11 p.m.

Brilliant. This is what I have been trying to find for hours, even days. Thankyou.
Here's a question though, given a d3 axis which renders tick marks like:
<g class="tick" opacity="1" transform="translate(388.5,0)">
<line stroke="currentColor" y2="-6"></line>
<text fill="currentColor" y="-9" dy="0em">100</text>
</g>

I used
document.getElementsByClassName("tick")[10].children[1].setAttributeNS(null, 'transform', `translate(${-8},0)`);

to move the last '100' text for the tick slightly left so I could see it. Great.
I did wonder though if I could access the <text > element using 'text' rather than the array index of 1.
So something like:
document.getElementsByClassName("tick")[10].children['text'].setAttributeNS(null, 'transform', `translate(${-8},0)`);
This didn't work, but I'm sure there must be a way :)
(oh I've used back tick notation for the translate as I might use a variable name rather than a literal -8.
I only accomplished this through your help so I'm very grateful for your efforts. :)

Peter on 24 May 2020, 5:35 p.m.

Hi Gordon,

You should be able to use getElementsByTagName on the results of getElementsByClassName("tick")[10].

So, something like this should work:
document.getElementsByClassName("tick")[10].getElementsByTagName('text')[0].setAttributeNS(...)

Cory on 20 Jun 2020, 5:48 p.m.

Peter, thanks for posting this, super helpful. It's what I needed to get started and see how to manipulate svg elements via javascript. I now have an interactive map of our neighborhood with hover and click functionality all self-contained in a single svg image.

Mayur on 11 Sep 2020, 5:14 a.m.

How can i change the colors of the svg file in javascript

Tags

Javascript svg