[jquery] jquery's append not working with svg element?

Assuming this:

<html>
<head>
 <script type="text/javascript" src="jquery.js"></script>
 <script type="text/javascript">
 $(document).ready(function(){
  $("svg").append('<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>');
 });
 </script>
</head>
<body>
 <svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100" width="200px" height="100px">
 </svg>
</body>

Why don't I see anything?

This question is related to jquery html svg

The answer is


I haven't seen someone mention this method but document.createElementNS() is helpful in this instance.

You can create the elements using vanilla Javascript as normal DOM nodes with the correct namespace and then jQuery-ify them from there. Like so:

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

var $circle = $(circle).attr({ //All your attributes });

$(svg).append($circle);

The only down side is that you have to create each SVG element with the right namespace individually or it won't work.


If the string you need to append is SVG and you add the proper namespace, you can parse the string as XML and append to the parent.

var xml = jQuery.parseXML('<circle xmlns="http://www.w3.org/2000/svg" cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>');
$("svg").append(xml.documentElement))

The accepted answer shows too complicated way. As Forresto claims in his answer, "it does seem to add them in the DOM explorer, but not on the screen" and the reason for this is different namespaces for html and svg.

The easiest workaround is to "refresh" whole svg. After appending circle (or other elements), use this:

$("body").html($("body").html());

This does the trick. The circle is on the screen.

Or if you want, use a container div:

$("#cont").html($("#cont").html());

And wrap your svg inside container div:

<div id="cont">
    <svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100" width="200px" height="100px">
    </svg>
</div>

The functional example:
http://jsbin.com/ejifab/1/edit

The advantages of this technique:

  • you can edit existing svg (that is already in DOM), eg. created using Raphael or like in your example "hard coded" without scripting.
  • you can add complex element structures as strings eg. $('svg').prepend('<defs><marker></marker><mask></mask></defs>'); like you do in jQuery.
  • after the elements are appended and made visible on the screen using $("#cont").html($("#cont").html()); their attributes can be edited using jQuery.

EDIT:

The above technique works with "hard coded" or DOM manipulated ( = document.createElementNS etc.) SVG only. If Raphael is used for creating elements, (according to my tests) the linking between Raphael objects and SVG DOM is broken if $("#cont").html($("#cont").html()); is used. The workaround to this is not to use $("#cont").html($("#cont").html()); at all and instead of it use dummy SVG document.

This dummy SVG is first a textual representation of SVG document and contains only elements that are needed. If we want eg. to add a filter element to Raphael document, the dummy could be something like <svg id="dummy" style="display:none"><defs><filter><!-- Filter definitons --></filter></defs></svg>. The textual representation is first converted to DOM using jQuery's $("body").append() method. And when the (filter) element is in DOM, it can be queried using standard jQuery methods and appended to the main SVG document which is created by Raphael.

Why this dummy is needed? Why not to add a filter element strictly to Raphael created document? If you try it using eg. $("svg").append("<circle ... />"), it is created as html element and nothing is on screen as described in answers. But if the whole SVG document is appended, then the browser handles automatically the namespace conversion of all the elements in SVG document.

An example enlighten the technique:

// Add Raphael SVG document to container element
var p = Raphael("cont", 200, 200);
// Add id for easy access
$(p.canvas).attr("id","p");
// Textual representation of element(s) to be added
var f = '<filter id="myfilter"><!-- filter definitions --></filter>';

// Create dummy svg with filter definition 
$("body").append('<svg id="dummy" style="display:none"><defs>' + f + '</defs></svg>');
// Append filter definition to Raphael created svg
$("#p defs").append($("#dummy filter"));
// Remove dummy
$("#dummy").remove();

// Now we can create Raphael objects and add filters to them:
var r = p.rect(10,10,100,100);
$(r.node).attr("filter","url(#myfilter)");

Full working demo of this technique is here: http://jsbin.com/ilinan/1/edit.

( I have (yet) no idea, why $("#cont").html($("#cont").html()); doesn't work when using Raphael. It would be very short hack. )


This is working for me today with FF 57:

function () {
    // JQuery, today, doesn't play well with adding SVG elements - tricks required
    $(selector_to_node_in_svg_doc).parent().prepend($(this).clone().text("Your"));
    $(selector_to_node_in_svg_doc).text("New").attr("x", "340").text("New")
        .attr('stroke', 'blue').attr("style", "text-decoration: line-through");
}

Makes:

this SVG image as seen in Firefox 57


Based on @chris-dolphin 's answer but using helper function:

// Creates svg element, returned as jQuery object
function $s(elem) {
  return $(document.createElementNS('http://www.w3.org/2000/svg', elem));
}

var $svg = $s("svg");
var $circle = $s("circle").attr({...});
$svg.append($circle);

 var svg; // if you have variable declared and not assigned value.
 // then you make a mistake by appending elements to that before creating element    
 svg.appendChild(document.createElement("g"));
 // at some point you assign to svg
 svg = document.createElementNS('http://www.w3.org/2000/svg', "svg")
 // then you put it in DOM
 document.getElementById("myDiv").appendChild(svg)
 // it wont render unless you manually change myDiv DOM with DevTools

// to fix assign before you append
var svg = createElement("svg", [
    ["version", "1.2"],
    ["xmlns:xlink", "http://www.w3.org/1999/xlink"],
    ["aria-labelledby", "title"],
    ["role", "img"],
    ["class", "graph"]
]);
function createElement(tag, attributeArr) {
      // .createElementNS  NS is must! Does not draw without
      let elem = document.createElementNS('http://www.w3.org/2000/svg', tag);             
      attributeArr.forEach(element => elem.setAttribute(element[0], element[1]));
      return elem;
}
// extra: <circle> for example requires attributes to render. Check if missing.

Found an easy way which works with all browsers I have (Chrome 49, Edge 25, Firefox 44, IE11, Safari 5 [Win], Safari 8 (MacOS)) :

_x000D_
_x000D_
// Clean svg content (if you want to update the svg's objects)_x000D_
// Note : .html('') doesn't works for svg in some browsers_x000D_
$('#svgObject').empty();_x000D_
// add some objects_x000D_
$('#svgObject').append('<polygon class="svgStyle" points="10,10 50,10 50,50 10,50 10,10" />');_x000D_
$('#svgObject').append('<circle class="svgStyle" cx="100" cy="30" r="25"/>');_x000D_
_x000D_
// Magic happens here: refresh DOM (you must refresh svg's parent for Edge/IE and Safari)_x000D_
$('#svgContainer').html($('#svgContainer').html());
_x000D_
.svgStyle_x000D_
{_x000D_
  fill:cornflowerblue;_x000D_
  fill-opacity:0.2;_x000D_
  stroke-width:2;_x000D_
  stroke-dasharray:5,5;_x000D_
  stroke:black;_x000D_
}
_x000D_
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>_x000D_
_x000D_
<div id="svgContainer">_x000D_
  <svg id="svgObject" height="100" width="200"></svg>_x000D_
</div>_x000D_
_x000D_
<span>It works if two shapes (one square and one circle) are displayed above.</span>
_x000D_
_x000D_
_x000D_


A much simpler way is to just generate your SVG into a string, create a wrapper HTML element and insert the svg string into the HTML element using $("#wrapperElement").html(svgString). This works just fine in Chrome and Firefox.


The accepted answer by Bobince is a short, portable solution. If you need to not only append SVG but also manipulate it, you could try the JavaScript library "Pablo" (I wrote it). It will feel familiar to jQuery users.

Your code example would then look like:

$(document).ready(function(){
    Pablo("svg").append('<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>');
});

You can also create SVG elements on the fly, without specifying markup:

var circle = Pablo.circle({
    cx:100,
    cy:50,
    r:40
}).appendTo('svg');

I can see circle in firefox, doing 2 things:

1) Renaming file from html to xhtml

2) Change script to

<script type="text/javascript">
$(document).ready(function(){
    var obj = document.createElementNS("http://www.w3.org/2000/svg", "circle");
    obj.setAttributeNS(null, "cx", 100);
    obj.setAttributeNS(null, "cy", 50);
    obj.setAttributeNS(null, "r",  40);
    obj.setAttributeNS(null, "stroke", "black");
    obj.setAttributeNS(null, "stroke-width", 2);
    obj.setAttributeNS(null, "fill", "red");
    $("svg")[0].appendChild(obj);
});
</script>

JQuery can't append elements to <svg> (it does seem to add them in the DOM explorer, but not on the screen).

One workaround is to append an <svg> with all of the elements that you need to the page, and then modify the attributes of the elements using .attr().

$('body')
  .append($('<svg><circle id="c" cx="10" cy="10" r="10" fill="green" /></svg>'))
  .mousemove( function (e) {
      $("#c").attr({
          cx: e.pageX,
          cy: e.pageY
      });
  });

http://jsfiddle.net/8FBjb/1/


The increasingly popular D3 library handles the oddities of appending/manipulating svg very nicely. You may want to consider using it as opposed to the jQuery hacks mentioned here.

HTML

<svg xmlns="http://www.w3.org/2000/svg"></svg>

Javascript

var circle = d3.select("svg").append("circle")
    .attr("r", "10")
    .attr("style", "fill:white;stroke:black;stroke-width:5");

I would suggest it might be better to use ajax and load the svg element from another page.

$('.container').load(href + ' .svg_element');

Where href is the location of the page with the svg. This way you can avoid any jittery effects that might occur from replacing the html content. Also, don't forget to unwrap the svg after it's loaded:

$('.svg_element').unwrap();

Examples related to jquery

How to make a variable accessible outside a function? Jquery assiging class to th in a table Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Getting all files in directory with ajax Bootstrap 4 multiselect dropdown Cross-Origin Read Blocking (CORB) bootstrap 4 file input doesn't show the file name Jquery AJAX: No 'Access-Control-Allow-Origin' header is present on the requested resource how to remove json object key and value.?

Examples related to html

Embed ruby within URL : Middleman Blog Please help me convert this script to a simple image slider Generating a list of pages (not posts) without the index file Why there is this "clear" class before footer? Is it possible to change the content HTML5 alert messages? Getting all files in directory with ajax DevTools failed to load SourceMap: Could not load content for chrome-extension How to set width of mat-table column in angular? How to open a link in new tab using angular? ERROR Error: Uncaught (in promise), Cannot match any routes. URL Segment

Examples related to svg

How to extract svg as file from web page How to display svg icons(.svg files) in UI using React Component? Why don’t my SVG images scale using the CSS "width" property? How to show SVG file on React Native? Easiest way to use SVG in Android? IE11 meta element Breaks SVG img src SVG changing the styles with CSS How to use SVG markers in Google Maps API v3 Embedding SVG into ReactJS How to change the color of an svg element?