DEV Community

Gavin Sykes
Gavin Sykes

Posted on • Edited on

Appending a Child to an SVG using Pure Javascript

Why write a blog post about this, Gavin? Well it turns out that appending a child to an SVG, or within an SVG group, isn't as simple as it would be if we were working with plain HTML.

I'm sure there are good reasons for this, but part of me would really like to know what those reasons are! I imagine they will be something to do with SVGs being written in XML rather than HTML. That, however, is research for another day, unless someone reading this knows the answer in which case all comments are welcome.

Well, why not just use jQuery or D3? They both have an append function and it works just fine.

They do indeed, however, for this particular project I want something that ships as light as possible, and as light and as powerful as jQuery and D3 are, it would still mean downloading an entire library just for the sake of appending elements to an SVG, whereas this entire project (so far) has 21 lines of JavaScript. I see an increasing number of answers on Stack Overflow these days saying "just use jQuery or D3", and the response from the OP being "that's not in the spec of the project".

Fear not though, because if you've ever tried the below code:

let newElement = document.createElement('rect');
newElement.setAttribute('fill','orange');
newElement.setAttribute('width','200');
newElement.setAttribute('height','200');
document.getElementById('svg-drawing').appendChild(newElement);
Enter fullscreen mode Exit fullscreen mode

Only for nothing to appear, despite it appearing in the DOM (which really does call into question what those good reasons could possibly be), then look no further, I have the solution.

To really work properly with SVGs we have to enter the world of namespaces. Indeed, this is exactly what jQuery and D3 do. By making one small change to our code we can make it work as it should, observe below:

let newElement = document.createElementNS('http://www.w3.org/2000/svg','rect');
newElement.setAttribute('fill','orange');
newElement.setAttribute('width','200');
newElement.setAttribute('height','200');
document.getElementById('svg-drawing').appendChild(newElement);
Enter fullscreen mode Exit fullscreen mode

Now, the differences under the hood between createElementNS and createElement, I couldn't tell you, though it's clear that it somehow tells the browser it is creating an SVG rectangle element, rather than just a rectangle element to go, um, somewhere else, somehow, I guess.

And that's basically it! I've taken the liberty of writing a helpful function to append an object to an SVG, as I'm sure you won't want to be typing, or even copying and pasting, that URL each time.

function appendSVGChild(elementType,target,attributes = {},text = '') {
  const element = document.createElementNS('http://www.w3.org/2000/svg',elementType);
  Object.entries(attributes).map(a => element.setAttribute(a[0],a[1]));
  if (text) {
    const textNode = document.createTextNode(text);
    element.appendChild(textNode);
  }
  target.appendChild(element);
  return element;
};

// If applying attributes, they need to be in the format {'attr':'val','attr2':'val2',...}
Enter fullscreen mode Exit fullscreen mode

Thoughts? Comments? Improvements? Improvements especially welcome as I'm sure there are some to be made!

UPDATE As I now use TypeScript wherever possible, I have taken the liberty of rewriting the function in TypeScript below:

function appendSVGChild(elementType: string, target: HTMLElement | SVGElement,attributes: Record<string, unknown> = {}, text = '') {
  const element: SVGElement = document.createElementNS('http://www.w3.org/2000/svg',elementType);
  Object.entries(attributes).map(a => element.setAttribute(a[0],a[1] as string));
  if (text) {
    const textNode = document.createTextNode(text);
    element.appendChild(textNode);
  }
  target.appendChild(element);
  return element;
};

// If applying attributes, they need to be in the format {'attr':'val','attr2':'val2',...}
Enter fullscreen mode Exit fullscreen mode

Top comments (9)

Collapse
 
tomhermans profile image
tom hermans

I KNEW this !
Yet it didn't stop me being baffled for over an hour why my created group wasn't showing.

var group = document.createElementNS(svgns, "g");

NOT
var group = document.createElementNS(svg, "g");

when you defined a string S.V.G.N.S. with the namespace string!!
Thanks Gavin. This article helped me hunt my mistake down

Collapse
 
dchelnokov profile image
Dmitry Chelnokov

Amazing! Oh, this post saved my evening :) How would someone know about something like using createElementNS.. :)

Collapse
 
zerogiven profile image
CSoellinger

Oh and at the third line should be

Object.entries(attributes).map(a => element.setAttribute(a[0],a[1]));
(JS Version)

Object.entries(attributes).map(a => element.setAttribute(a[0],a[1] as string));
(TS Version)

Collapse
 
gavinsykes profile image
Gavin Sykes

My reply to your earlier comment applies here too!

Collapse
 
trentungard profile image
trentungard

I just stumbled across this and it saved my sanity. Thank you!

Collapse
 
gavinsykes profile image
Gavin Sykes

You're welcome! I was starting to lose sanity myself before I figured it out!

Collapse
 
voneiden profile image
Matti Eiden

Phew, thanks! I knew setAttributeNS already, but missed that there's also a createElementNS!

Collapse
 
zerogiven profile image
CSoellinger

At the JS function the second line should be

const element = document.createElementNS('w3.org/2000/svg', elementType);

Little correction for the copy&paste fools like me :D

Collapse
 
gavinsykes profile image
Gavin Sykes

Right you are, thank you and apologies! I made the mistake of renaming things halfway through editing!