Monday, January 6, 2014

Dynamically injecting elements into existing SVG

It might happen that when dealing with SVG in HTML page you will need to inject certain SVG tags somewhere inside of existing SVG element. It might seems straightforward and one would think that standard JS document.createElement will do the trick. Well, not quite... Yes, it will insert element into DOM and when inspected the element will be there as it should, however the SVG displayed will not change. Consider the code below with single SVG element with image. We will try to add mask to it:

HTML:

<svg height="600" version="1.1" width="800" xmlns="http://www.w3.org/2000/svg" style="overflow: hidden; position: relative;">      
    
    <image x="50" y="50" width="120" height="120" preserveAspectRatio="none" xlink:href="https://lh3.googleusercontent.com/-0afLqDT73Yk/AAAAAAAAAAI/AAAAAAAAAH0/wf_rwvjAuOY/s120-c/photo.jpg" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);" fill="#008000" mask="url(#mask)"></image>
    
</svg>

JavaScript where we injecting <mask ...> tag into HTML above:

// create two elements
var mask = document.createElement('mask');
var circle = document.createElement('circle');

// apply attributes and what not
mask.setAttribute("maskUnits","objectBoundingBox");
mask.setAttribute("maskContentUnits","objectBoundingBox");
mask.setAttribute("id", "mask");
mask.setAttribute("x", "0");
mask.setAttribute("y", "0");
mask.setAttribute("width", "1");
mask.setAttribute("height", "1");

circle.setAttribute("cx", "0.5");
circle.setAttribute("cy", "0.5");
circle.setAttribute("r", "0.5");
circle.setAttribute("fill", "white");

mask.appendChild(circle);

document.getElementsByTagName("svg")[0].appendChild(mask);

The code above will inject <mask ...> and <circle...> tags into DOM but the mask will have no effect. One dirty method is to remove SVG element from DOM and insert it again e.g. with jQuery call $("#elementId").html($("#elementId").html()); but its dirty workaround IMO. The fix to above code is as simple. All needs to be done  is to use document.createElementNS rather than document.createElement. Long story short this will create element with specified namespace. If you are a geek then have a look at MDN where createElementNS is documented or if you are true nerd with no girlfriend, thick glasses and nothing better to do check out namespaces in XML. For all the rest looking for quick fix simply have a look at code below. The only difference is document.createElementNS where we need to pass namespace URI as well as element's name we want to create:

var mask = document.createElementNS("http://www.w3.org/2000/svg", 'mask');
var circle = document.createElementNS("http://www.w3.org/2000/svg", 'circle');

circle.setAttribute("cx", "0.5");
circle.setAttribute("cy", "0.5");
circle.setAttribute("r", "0.5");
circle.setAttribute("fill", "white");

mask.setAttribute("maskUnits","objectBoundingBox");
mask.setAttribute("maskContentUnits","objectBoundingBox");
mask.setAttribute("id", "mask");
mask.setAttribute("x", "0");
mask.setAttribute("y", "0");
mask.setAttribute("width", "1");
mask.setAttribute("height", "1");

mask.appendChild(circle);

document.getElementsByTagName("svg")[0].appendChild(mask);

I hope above will save someone trouble of googling this stuff any further. Oy yeh, no need to copy/paste this stuff, fiddle is here.

No comments:

Post a Comment