Including JavaScript in SVG - javascript

I am trying to create an interactive SVG code with JavaScript, by embedding the JavaScript in the SVG. I don't know if this is the right way to do this:
<?xml version="1.0" encoding="UTF-8" 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"
onkeypress="move()">
<script type="text/javascript">
<![CDATA[
var x;
var y;
function move()
{
x = new Number(svg.getElementsByTagName("circle")[0].getAttribute("cx"));
y = new Number (svg.getElementsByTagName("circle")[0].getAttribute("cy"));
switch (event.keyCode)
{
case 119:
y--;
y = y.toString();
svg.getElementsByTagName("circle").setAttribute("cy",y);
break;
case 115:
y++;
y = y.toString();
svg.getElementsByTagName("circle").setAttribute("cy",y);
break;
case 97:
x--;
x = x.toString();
svg.getElementsByTagName("circle").setAttribute("cx",x);
break;
case 100:
x++;
x = x.toString();
svg.getElementsByTagName("circle").setAttribute("cx",x);
break;
default:
}
}
]]>
</script>
<rect x="0" y="0" height="500" width="500" style="stroke-width:1; stroke:black; fill:white"></rect>
<circle cx="250" cy="250" r="50" stroke="red" stroke-width="1" fill="red"></circle>
</svg>
It is supposed to have a ball that moves with wasd, but the ball doesn't move. What am I doing wrong?

Here is a working version as I would write it:
<!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">
<circle cx="250" cy="250" r="50" fill="red" />
<script type="text/javascript"><![CDATA[
var KEY = { w:87, a:65, s:83, d:68 };
var moveSpeed = 5;
var circle = document.getElementsByTagName("circle")[0];
var x = circle.getAttribute('cx')*1,
y = circle.getAttribute('cy')*1;
document.documentElement.addEventListener('keydown',function(evt){
switch (evt.keyCode){
case KEY.w:
circle.setAttribute('cy',y-=moveSpeed);
// Alternatively:
// circle.cy.baseVal.value = (y-=moveSpeed);
break;
case KEY.s:
circle.setAttribute('cy',y+=moveSpeed);
break;
case KEY.a:
circle.setAttribute('cx',x-=moveSpeed);
break;
case KEY.d:
circle.setAttribute('cx',x+=moveSpeed);
break;
}
},false);
]]></script>
</svg>
Some notes:
Don't re-get the reference to the circle again and again. Making your code DRY makes it more robust, less typing, and (in this case) faster to execute.
Edit: If you cannot figure out how to do this given my code above, post any code that is not working for you.
Don't rely on a global event object; that's old IE nonsense. Use the event object passed to your event handler.
Edit: If you reference event in your code with no parameter or local variable by that name, you are assuming that there will be a global event object set. Instead, see the code I wrote for you, which shows that the event handler is passed an event object. By giving that a name, such as I gave it the name evt, you are receiving an event object specific to your event handler.
Since you are modifying the x and y variables, there's no need to re-get the cx and cy attributes each key press.
Edit: In your original code and the answer you accepted, you have declared var x outside your event handler, and you have x = ... at the start of your event handler, and then x++ in one of the event handlers. You can either re-get the current value of x each time (as you did) and then setAttribute(...,x+1), or (as I did) you can only fetch the value of the attribute once before the event handlers and then assume that this value is correct each time you handle the key event.
Don't put your JavaScript event handlers on your elements, attach them programmatically.
Edit: In your SVG markup you have: <svg ... onkeypress="move()">. Mixing your behavior with your markup is a really bad idea in HTML, and a bad idea in SVG. Instead of using onfoo="..." attributes to describe what should happen when an event occurs on an element, instead use addEventListner() to attach the event handlers via code, without editing your SVG markup.
There's no need to coerce the numbers to strings before setting them as attributes.
Use keydown and the ASCII event codes I supplied above instead of keypress and the odd numbers you were using if you want it to work in all browsers.
Edit: You complained in another post that you cannot do this because you want the event handler to be processed repeatedly as the key is held down. Note that your desired behavior is achieved with my sample code in Chrome, Safari, Firefox, and IE (I don't have Opera to test). In other words keydown works as you wanted, despite how you thought it should behave.
Edit 2: If you want to include a script block at the top of your document, before all elements have necessarily been created, you can do something like the following:
<svg ...>
<script type="text/javascript">
window.addEventListener('load',function(){
var circle = ...;
document.rootElement.addEventListener('keydown',function(evt){
...
},false);
},false);
</script>
...
</svg>
The outer function will only run once the page has loaded, so you can be sure that the elements exist to reference them.

you can add script js into svg code by declare script tag into svg tag:
<svg version="1.1" id="loader-1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="40px" height="40px" viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
...
<script type="text/javascript">
window.addEventListener('load',function(){
alert('Hi')
})
</script>
</svg>

This works in Chrome. You had a few errors, like indexing getElementsByTagName only sometimes. Plus the big problem was that the onkeypress attribute wasn't binding.
<?xml version="1.0" encoding="UTF-8" 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"
>
<script type="text/javascript">
<![CDATA[
var x;
var y;
function move()
{
x = new Number(document.getElementsByTagName("circle")[0].getAttribute("cx"));
y = new Number (document.getElementsByTagName("circle")[0].getAttribute("cy"));
switch (event.keyCode)
{
case 119:
y--;
y = y.toString();
document.getElementsByTagName("circle")[0].setAttribute("cy",y);
break;
case 115:
y++;
y = y.toString();
document.getElementsByTagName("circle")[0].setAttribute("cy",y);
break;
case 97:
x--;
x = x.toString();
document.getElementsByTagName("circle")[0].setAttribute("cx",x);
break;
case 100:
x++;
x = x.toString();
document.getElementsByTagName("circle")[0].setAttribute("cx",x);
break;
default:
}
}
document.documentElement.addEventListener("keypress", move);
]]>
</script>
<rect x="0" y="0" height="500" width="500" style="stroke-width:1; stroke:black; fill:white"></rect>
<circle cx="250" cy="250" r="50" stroke="red" stroke-width="1" fill="red"></circle>
</svg>

Simple Example
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<path d="M90,18c-90-45-115,102,0,69v-21l4-3h-23l-8,4h16v19c-80,15-65-106,2-63l-4,5l4-1z" fill="#CCC" stroke="#DDD" stroke-width="2" stroke-linejoin="round"/>
<path d="M87,15c-90-45-115,102,0,69v-21l4-3h-23l-8,4h16v19c-80,15-65-106,2-63l-4,5l4-1z" fill="#00F"/>
<script>
alert("Hello world");
</script>
</svg>

Related

Adding masks to SVG and changing them [duplicate]

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?
When you pass a markup string into $, it's parsed as HTML using the browser's innerHTML property on a <div> (or other suitable container for special cases like <tr>). innerHTML can't parse SVG or other non-HTML content, and even if it could it wouldn't be able to tell that <circle> was supposed to be in the SVG namespace.
innerHTML is not available on SVGElement—it is a property of HTMLElement only. Neither is there currently an innerSVG property or other way(*) to parse content into an SVGElement. For this reason you should use DOM-style methods. jQuery doesn't give you easy access to the namespaced methods needed to create SVG elements. Really jQuery isn't designed for use with SVG at all and many operations may fail.
HTML5 promises to let you use <svg> without an xmlns inside a plain HTML (text/html) document in the future. But this is just a parser hack(**), the SVG content will still be SVGElements in the SVG namespace, and not HTMLElements, so you'll not be able to use innerHTML even though they look like part of an HTML document.
However, for today's browsers you must use XHTML (properly served as application/xhtml+xml; save with the .xhtml file extension for local testing) to get SVG to work at all. (It kind of makes sense to anyway; SVG is a properly XML-based standard.) This means you'd have to escape the < symbols inside your script block (or enclose in a CDATA section), and include the XHTML xmlns declaration. example:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"><head>
</head><body>
<svg id="s" xmlns="http://www.w3.org/2000/svg"/>
<script type="text/javascript">
function makeSVG(tag, attrs) {
var el= document.createElementNS('http://www.w3.org/2000/svg', tag);
for (var k in attrs)
el.setAttribute(k, attrs[k]);
return el;
}
var circle= makeSVG('circle', {cx: 100, cy: 50, r:40, stroke: 'black', 'stroke-width': 2, fill: 'red'});
document.getElementById('s').appendChild(circle);
circle.onmousedown= function() {
alert('hello');
};
</script>
</body></html>
*: well, there's DOM Level 3 LS's parseWithContext, but browser support is very poor. Edit to add: however, whilst you can't inject markup into an SVGElement, you could inject a new SVGElement into an HTMLElement using innerHTML, then transfer it to the desired target. It'll likely be a bit slower though:
<script type="text/javascript"><![CDATA[
function parseSVG(s) {
var div= document.createElementNS('http://www.w3.org/1999/xhtml', 'div');
div.innerHTML= '<svg xmlns="http://www.w3.org/2000/svg">'+s+'</svg>';
var frag= document.createDocumentFragment();
while (div.firstChild.firstChild)
frag.appendChild(div.firstChild.firstChild);
return frag;
}
document.getElementById('s').appendChild(parseSVG(
'<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red" onmousedown="alert(\'hello\');"/>'
));
]]></script>
**: I hate the way the authors of HTML5 seem to be scared of XML and determined to shoehorn XML-based features into the crufty mess that is HTML. XHTML solved these problems years ago.
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. )
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");
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/
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.
Found an easy way which works with all browsers I have (Chrome 49, Edge 25, Firefox 44, IE11, Safari 5 [Win], Safari 8 (MacOS)) :
// Clean svg content (if you want to update the svg's objects)
// Note : .html('') doesn't works for svg in some browsers
$('#svgObject').empty();
// add some objects
$('#svgObject').append('<polygon class="svgStyle" points="10,10 50,10 50,50 10,50 10,10" />');
$('#svgObject').append('<circle class="svgStyle" cx="100" cy="30" r="25"/>');
// Magic happens here: refresh DOM (you must refresh svg's parent for Edge/IE and Safari)
$('#svgContainer').html($('#svgContainer').html());
.svgStyle
{
fill:cornflowerblue;
fill-opacity:0.2;
stroke-width:2;
stroke-dasharray:5,5;
stroke:black;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="svgContainer">
<svg id="svgObject" height="100" width="200"></svg>
</div>
<span>It works if two shapes (one square and one circle) are displayed above.</span>
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>
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);
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');
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)
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();
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.
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.
I have made a small function for that. As for jQuery append method, the problem is the requirement for specifying namespace for SVG which is "http://www.w3.org/2000/svg" More
So what if I prepare it for append method? In that case the only thing you need to offer is some parameters like:
tagName: It can be every SVG element like rect, circle, text, g etc.
text: If you are using something like text tagname, you'll need to specify text
And other known attributes for an SVG elements.
Thus what I'm going to do is defining a function named createSvgElem() which uses document.createElementNS() internally.
Here is an example:
$("svg").append(
createSvgElem({tagName: "text", x: 10, y: 10, text: "ABC", style: "fill: red"})
)
And here's the function:
function createSvgElem(options){
var settings = $.extend({
}, options);
if(!$.isEmptyObject(settings.tagName)){
var el = document.createElementNS('http://www.w3.org/2000/svg', settings.tagName);
for (var k in settings)
if(k != "tagName" && k != "text" && settings[k] != "")//If attribute has value
el.setAttribute(k, settings[k]);
if ("text" in settings)
el.textContent = settings.text; //el.innerText; For IE
return el;
}
}
Here you can try it yourself:
//Definition:
function createSvgElem(options){
var settings = $.extend({
}, options);
if(!$.isEmptyObject(settings.tagName)){
var el = document.createElementNS('http://www.w3.org/2000/svg', settings.tagName);
for (var k in settings)
if(k != "tagName" && k != "text" && settings[k] != "")//If attribute has value
el.setAttribute(k, settings[k]);
if ("text" in settings)
el.textContent = settings.text; //el.innerText; For IE
return el;
}
}
//Usage:
$(function(){
$("#svg-elem").append(
createSvgElem({tagName: "rect", width: 130, height: 500, style: "fill: #000000a3;"})
)
$("#svg-elem").append(
createSvgElem({tagName: "text", x: 30, y: 30, text: "ABCD", style: "fill: red"})
)
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<svg id="svg-elem" width="200" height="200">
</svg>
With jquery you can do just that.
setting the DataType to 'text'.
$.ajax({
url: "url-to-svg.svg",
dataType : 'text'
})
.done(function(svg) {
let svg_live = $(svg);
svg_live.append('<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>');
$('#selector-id').html(svg_live);
});
Alternative 1: native js insertAdjacentHTML()
Provided you don't consider switching to native JavaScript at all ...
You could also use native javaScript method insertAdjacentHTML() for similarly convenient notation.
$("#svg")[0].insertAdjacentHTML(
"beforeEnd",
'<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'
);
$("#svg")[0] makes your jQuery object selectable in native JS.
Alternative 2: write a native js DOMParser() helper
mdn web docs: DOMParser.parseFromString()
function createSvgEl(markup) {
markup = `<svg xmlns="http://www.w3.org/2000/svg">
${markup}</svg>`;
const svgEl = new DOMParser().parseFromString(markup, "image/svg+xml")
.documentElement.children[0];
return svgEl;
}
Usage in jQuery:
$("#svgXML").append(
createSvgEl(
'<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'
)
);
Demo
// native js helper
function createSvgEl(markup) {
markup = `<svg xmlns="http://www.w3.org/2000/svg">
${markup}</svg>`;
const svgEl = new DOMParser().parseFromString(markup, "image/svg+xml")
.documentElement.children[0];
return svgEl;
}
$(document).ready(function() {
// works - but will remove existing children
$("#svg1").html(
'<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'
);
// works
// $("#svg")[0] makes your jQueryObject selectable in native JS
$("#svg")[0].insertAdjacentHTML(
"beforeEnd",
'<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'
);
$("#svgXML").append(
createSvgEl(
'<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'
)
);
// jquery still works! Vanilla doesn't harm!
$("#svgXML circle:nth-of-type(2)").attr('fill', 'orange');
//insert after()
$("#svgAfter circle").after(
createSvgEl(
'<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'
)
);
//insert after native
$("#svgAfterNative circle")[0].insertAdjacentHTML(
"afterEnd",
'<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'
);
});
svg {
border: 1px solid red;
overflow: visible;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>Append via native js insertAdjacentHTML()</p>
<svg id="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">
<circle cx="10" cy="10" r="5" fill="green" />
</svg>
<p>Append via DOMParser() helper</p>
<svg id="svgXML" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100" width="200px" height="100px">
<circle cx="10" cy="10" r="5" fill="green" />
</svg>
<p>Append via jquery html() - will strip existing child nodes</p>
<svg id="svg1" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100" width="200px" height="100px">
<circle cx="10" cy="10" r="5" fill="green" />
</svg>
<p>Insert after existing element with jQuery after() using DOMParser() helper</p>
<svg id="svgAfter" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100" width="200px" height="100px">
<circle cx="10" cy="10" r="5" fill="green" />
</svg>
<p>Insert after existing element with native js insertAdjacentHTML()</p>
<svg id="svgAfterNative" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100" width="200px" height="100px">
<circle cx="10" cy="10" r="5" fill="green" />
</svg>
jquery's after() or before() methods will also fail to add a SVG DOM (depending on the correct namespace) element.
Using the aforementioned workarounds will fix this issue as well.
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:

How to interrogate snap svg event object to get viewbox coordinate?

I have a simple svg file containing 2 rectangles and a text :
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg id="mySvg" onload="init()" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="100%" width="100%" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" viewBox="0 0 1371.4286 773.5047">
<script xlink:href="snap.svg-min.js" type="text/ecmascript"/>
<rect id="blackRect" style="color-rendering:auto;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;image-rendering:auto" ry=".16344" height="773.5" width="1371.4" y="161.71" x="-371.43" fill="#ffb380"/>
<rect id="hoverTarget" style="color-rendering:auto;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;image-rendering:auto" ry=".098402" height="177.14" width="286.62" y="369.51" x="180" fill="#008080"/>
<text id="viewboxText" style="word-spacing:0px;letter-spacing:0px" font-weight="bold" xml:space="preserve" font-size="27.5px" line-height="125%" y="810.93359" x="-3.165039e-005" font-family="sans-serif" fill="#000000"><tspan id="tspan3348" x="-3.165039e-005" y="810.93359">Viewbox :</tspan></text>
</svg>
and before the closing </svg> tag i added this script:
<script><![CDATA[
var mySvg, hoverTarget, viewboxText;
function init(){
mySvg=Snap("#mySvg");
hoverTarget=mySvg.select("#hoverTarget");
viewboxText=mySvg.select("#viewboxText");
hoverTarget.mousemove(hoverCursor);
}
function hoverCursor(evt){
var cursorX, cursorY;
if(evt.type==="mousemove"){
cursorX=...;
/*this is my question : how to interrogate the evt object to get X position of the cursor
according to mySvg viewbox?*/
cursorY=...;
//and, of course, how to get Y position from the evt object..
//and after that, display it on the text...
viewboxText.attr({text : "X : " + cursorX + ", Y : " + cursorY});
}
}
]]></script>
My question is : is there any way to interrogate snap event object of the event handler function to obtain viewbox coordinate of the svg parent element?
If there is, what is the syntax?
This is what I think you may be after...
First we get the inverse matrix from the element to the screen
var m = Snap.matrix( evt.target.getScreenCTM().inverse() )
// you may want to use this instead, but should be same for this case
// var m = Snap.matrix( mySvg.node.getScreenCTM().inverse() )
var newX = m.x( cursorX, cursorY )
var newY = m.y( cursorX, cursorY )
m.x(), m.y() convert the x,y coordinates with the new matrix.
jsfiddle

Generating SVG with Javascript [duplicate]

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?
When you pass a markup string into $, it's parsed as HTML using the browser's innerHTML property on a <div> (or other suitable container for special cases like <tr>). innerHTML can't parse SVG or other non-HTML content, and even if it could it wouldn't be able to tell that <circle> was supposed to be in the SVG namespace.
innerHTML is not available on SVGElement—it is a property of HTMLElement only. Neither is there currently an innerSVG property or other way(*) to parse content into an SVGElement. For this reason you should use DOM-style methods. jQuery doesn't give you easy access to the namespaced methods needed to create SVG elements. Really jQuery isn't designed for use with SVG at all and many operations may fail.
HTML5 promises to let you use <svg> without an xmlns inside a plain HTML (text/html) document in the future. But this is just a parser hack(**), the SVG content will still be SVGElements in the SVG namespace, and not HTMLElements, so you'll not be able to use innerHTML even though they look like part of an HTML document.
However, for today's browsers you must use XHTML (properly served as application/xhtml+xml; save with the .xhtml file extension for local testing) to get SVG to work at all. (It kind of makes sense to anyway; SVG is a properly XML-based standard.) This means you'd have to escape the < symbols inside your script block (or enclose in a CDATA section), and include the XHTML xmlns declaration. example:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"><head>
</head><body>
<svg id="s" xmlns="http://www.w3.org/2000/svg"/>
<script type="text/javascript">
function makeSVG(tag, attrs) {
var el= document.createElementNS('http://www.w3.org/2000/svg', tag);
for (var k in attrs)
el.setAttribute(k, attrs[k]);
return el;
}
var circle= makeSVG('circle', {cx: 100, cy: 50, r:40, stroke: 'black', 'stroke-width': 2, fill: 'red'});
document.getElementById('s').appendChild(circle);
circle.onmousedown= function() {
alert('hello');
};
</script>
</body></html>
*: well, there's DOM Level 3 LS's parseWithContext, but browser support is very poor. Edit to add: however, whilst you can't inject markup into an SVGElement, you could inject a new SVGElement into an HTMLElement using innerHTML, then transfer it to the desired target. It'll likely be a bit slower though:
<script type="text/javascript"><![CDATA[
function parseSVG(s) {
var div= document.createElementNS('http://www.w3.org/1999/xhtml', 'div');
div.innerHTML= '<svg xmlns="http://www.w3.org/2000/svg">'+s+'</svg>';
var frag= document.createDocumentFragment();
while (div.firstChild.firstChild)
frag.appendChild(div.firstChild.firstChild);
return frag;
}
document.getElementById('s').appendChild(parseSVG(
'<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red" onmousedown="alert(\'hello\');"/>'
));
]]></script>
**: I hate the way the authors of HTML5 seem to be scared of XML and determined to shoehorn XML-based features into the crufty mess that is HTML. XHTML solved these problems years ago.
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. )
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");
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/
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.
Found an easy way which works with all browsers I have (Chrome 49, Edge 25, Firefox 44, IE11, Safari 5 [Win], Safari 8 (MacOS)) :
// Clean svg content (if you want to update the svg's objects)
// Note : .html('') doesn't works for svg in some browsers
$('#svgObject').empty();
// add some objects
$('#svgObject').append('<polygon class="svgStyle" points="10,10 50,10 50,50 10,50 10,10" />');
$('#svgObject').append('<circle class="svgStyle" cx="100" cy="30" r="25"/>');
// Magic happens here: refresh DOM (you must refresh svg's parent for Edge/IE and Safari)
$('#svgContainer').html($('#svgContainer').html());
.svgStyle
{
fill:cornflowerblue;
fill-opacity:0.2;
stroke-width:2;
stroke-dasharray:5,5;
stroke:black;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="svgContainer">
<svg id="svgObject" height="100" width="200"></svg>
</div>
<span>It works if two shapes (one square and one circle) are displayed above.</span>
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>
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);
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');
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)
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();
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.
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.
I have made a small function for that. As for jQuery append method, the problem is the requirement for specifying namespace for SVG which is "http://www.w3.org/2000/svg" More
So what if I prepare it for append method? In that case the only thing you need to offer is some parameters like:
tagName: It can be every SVG element like rect, circle, text, g etc.
text: If you are using something like text tagname, you'll need to specify text
And other known attributes for an SVG elements.
Thus what I'm going to do is defining a function named createSvgElem() which uses document.createElementNS() internally.
Here is an example:
$("svg").append(
createSvgElem({tagName: "text", x: 10, y: 10, text: "ABC", style: "fill: red"})
)
And here's the function:
function createSvgElem(options){
var settings = $.extend({
}, options);
if(!$.isEmptyObject(settings.tagName)){
var el = document.createElementNS('http://www.w3.org/2000/svg', settings.tagName);
for (var k in settings)
if(k != "tagName" && k != "text" && settings[k] != "")//If attribute has value
el.setAttribute(k, settings[k]);
if ("text" in settings)
el.textContent = settings.text; //el.innerText; For IE
return el;
}
}
Here you can try it yourself:
//Definition:
function createSvgElem(options){
var settings = $.extend({
}, options);
if(!$.isEmptyObject(settings.tagName)){
var el = document.createElementNS('http://www.w3.org/2000/svg', settings.tagName);
for (var k in settings)
if(k != "tagName" && k != "text" && settings[k] != "")//If attribute has value
el.setAttribute(k, settings[k]);
if ("text" in settings)
el.textContent = settings.text; //el.innerText; For IE
return el;
}
}
//Usage:
$(function(){
$("#svg-elem").append(
createSvgElem({tagName: "rect", width: 130, height: 500, style: "fill: #000000a3;"})
)
$("#svg-elem").append(
createSvgElem({tagName: "text", x: 30, y: 30, text: "ABCD", style: "fill: red"})
)
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<svg id="svg-elem" width="200" height="200">
</svg>
With jquery you can do just that.
setting the DataType to 'text'.
$.ajax({
url: "url-to-svg.svg",
dataType : 'text'
})
.done(function(svg) {
let svg_live = $(svg);
svg_live.append('<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>');
$('#selector-id').html(svg_live);
});
Alternative 1: native js insertAdjacentHTML()
Provided you don't consider switching to native JavaScript at all ...
You could also use native javaScript method insertAdjacentHTML() for similarly convenient notation.
$("#svg")[0].insertAdjacentHTML(
"beforeEnd",
'<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'
);
$("#svg")[0] makes your jQuery object selectable in native JS.
Alternative 2: write a native js DOMParser() helper
mdn web docs: DOMParser.parseFromString()
function createSvgEl(markup) {
markup = `<svg xmlns="http://www.w3.org/2000/svg">
${markup}</svg>`;
const svgEl = new DOMParser().parseFromString(markup, "image/svg+xml")
.documentElement.children[0];
return svgEl;
}
Usage in jQuery:
$("#svgXML").append(
createSvgEl(
'<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'
)
);
Demo
// native js helper
function createSvgEl(markup) {
markup = `<svg xmlns="http://www.w3.org/2000/svg">
${markup}</svg>`;
const svgEl = new DOMParser().parseFromString(markup, "image/svg+xml")
.documentElement.children[0];
return svgEl;
}
$(document).ready(function() {
// works - but will remove existing children
$("#svg1").html(
'<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'
);
// works
// $("#svg")[0] makes your jQueryObject selectable in native JS
$("#svg")[0].insertAdjacentHTML(
"beforeEnd",
'<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'
);
$("#svgXML").append(
createSvgEl(
'<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'
)
);
// jquery still works! Vanilla doesn't harm!
$("#svgXML circle:nth-of-type(2)").attr('fill', 'orange');
//insert after()
$("#svgAfter circle").after(
createSvgEl(
'<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'
)
);
//insert after native
$("#svgAfterNative circle")[0].insertAdjacentHTML(
"afterEnd",
'<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'
);
});
svg {
border: 1px solid red;
overflow: visible;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>Append via native js insertAdjacentHTML()</p>
<svg id="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">
<circle cx="10" cy="10" r="5" fill="green" />
</svg>
<p>Append via DOMParser() helper</p>
<svg id="svgXML" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100" width="200px" height="100px">
<circle cx="10" cy="10" r="5" fill="green" />
</svg>
<p>Append via jquery html() - will strip existing child nodes</p>
<svg id="svg1" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100" width="200px" height="100px">
<circle cx="10" cy="10" r="5" fill="green" />
</svg>
<p>Insert after existing element with jQuery after() using DOMParser() helper</p>
<svg id="svgAfter" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100" width="200px" height="100px">
<circle cx="10" cy="10" r="5" fill="green" />
</svg>
<p>Insert after existing element with native js insertAdjacentHTML()</p>
<svg id="svgAfterNative" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100" width="200px" height="100px">
<circle cx="10" cy="10" r="5" fill="green" />
</svg>
jquery's after() or before() methods will also fail to add a SVG DOM (depending on the correct namespace) element.
Using the aforementioned workarounds will fix this issue as well.
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:

Appending elements in string format into SVG? [duplicate]

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?
When you pass a markup string into $, it's parsed as HTML using the browser's innerHTML property on a <div> (or other suitable container for special cases like <tr>). innerHTML can't parse SVG or other non-HTML content, and even if it could it wouldn't be able to tell that <circle> was supposed to be in the SVG namespace.
innerHTML is not available on SVGElement—it is a property of HTMLElement only. Neither is there currently an innerSVG property or other way(*) to parse content into an SVGElement. For this reason you should use DOM-style methods. jQuery doesn't give you easy access to the namespaced methods needed to create SVG elements. Really jQuery isn't designed for use with SVG at all and many operations may fail.
HTML5 promises to let you use <svg> without an xmlns inside a plain HTML (text/html) document in the future. But this is just a parser hack(**), the SVG content will still be SVGElements in the SVG namespace, and not HTMLElements, so you'll not be able to use innerHTML even though they look like part of an HTML document.
However, for today's browsers you must use XHTML (properly served as application/xhtml+xml; save with the .xhtml file extension for local testing) to get SVG to work at all. (It kind of makes sense to anyway; SVG is a properly XML-based standard.) This means you'd have to escape the < symbols inside your script block (or enclose in a CDATA section), and include the XHTML xmlns declaration. example:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"><head>
</head><body>
<svg id="s" xmlns="http://www.w3.org/2000/svg"/>
<script type="text/javascript">
function makeSVG(tag, attrs) {
var el= document.createElementNS('http://www.w3.org/2000/svg', tag);
for (var k in attrs)
el.setAttribute(k, attrs[k]);
return el;
}
var circle= makeSVG('circle', {cx: 100, cy: 50, r:40, stroke: 'black', 'stroke-width': 2, fill: 'red'});
document.getElementById('s').appendChild(circle);
circle.onmousedown= function() {
alert('hello');
};
</script>
</body></html>
*: well, there's DOM Level 3 LS's parseWithContext, but browser support is very poor. Edit to add: however, whilst you can't inject markup into an SVGElement, you could inject a new SVGElement into an HTMLElement using innerHTML, then transfer it to the desired target. It'll likely be a bit slower though:
<script type="text/javascript"><![CDATA[
function parseSVG(s) {
var div= document.createElementNS('http://www.w3.org/1999/xhtml', 'div');
div.innerHTML= '<svg xmlns="http://www.w3.org/2000/svg">'+s+'</svg>';
var frag= document.createDocumentFragment();
while (div.firstChild.firstChild)
frag.appendChild(div.firstChild.firstChild);
return frag;
}
document.getElementById('s').appendChild(parseSVG(
'<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red" onmousedown="alert(\'hello\');"/>'
));
]]></script>
**: I hate the way the authors of HTML5 seem to be scared of XML and determined to shoehorn XML-based features into the crufty mess that is HTML. XHTML solved these problems years ago.
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. )
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");
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/
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.
Found an easy way which works with all browsers I have (Chrome 49, Edge 25, Firefox 44, IE11, Safari 5 [Win], Safari 8 (MacOS)) :
// Clean svg content (if you want to update the svg's objects)
// Note : .html('') doesn't works for svg in some browsers
$('#svgObject').empty();
// add some objects
$('#svgObject').append('<polygon class="svgStyle" points="10,10 50,10 50,50 10,50 10,10" />');
$('#svgObject').append('<circle class="svgStyle" cx="100" cy="30" r="25"/>');
// Magic happens here: refresh DOM (you must refresh svg's parent for Edge/IE and Safari)
$('#svgContainer').html($('#svgContainer').html());
.svgStyle
{
fill:cornflowerblue;
fill-opacity:0.2;
stroke-width:2;
stroke-dasharray:5,5;
stroke:black;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="svgContainer">
<svg id="svgObject" height="100" width="200"></svg>
</div>
<span>It works if two shapes (one square and one circle) are displayed above.</span>
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>
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);
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');
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)
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();
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.
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.
I have made a small function for that. As for jQuery append method, the problem is the requirement for specifying namespace for SVG which is "http://www.w3.org/2000/svg" More
So what if I prepare it for append method? In that case the only thing you need to offer is some parameters like:
tagName: It can be every SVG element like rect, circle, text, g etc.
text: If you are using something like text tagname, you'll need to specify text
And other known attributes for an SVG elements.
Thus what I'm going to do is defining a function named createSvgElem() which uses document.createElementNS() internally.
Here is an example:
$("svg").append(
createSvgElem({tagName: "text", x: 10, y: 10, text: "ABC", style: "fill: red"})
)
And here's the function:
function createSvgElem(options){
var settings = $.extend({
}, options);
if(!$.isEmptyObject(settings.tagName)){
var el = document.createElementNS('http://www.w3.org/2000/svg', settings.tagName);
for (var k in settings)
if(k != "tagName" && k != "text" && settings[k] != "")//If attribute has value
el.setAttribute(k, settings[k]);
if ("text" in settings)
el.textContent = settings.text; //el.innerText; For IE
return el;
}
}
Here you can try it yourself:
//Definition:
function createSvgElem(options){
var settings = $.extend({
}, options);
if(!$.isEmptyObject(settings.tagName)){
var el = document.createElementNS('http://www.w3.org/2000/svg', settings.tagName);
for (var k in settings)
if(k != "tagName" && k != "text" && settings[k] != "")//If attribute has value
el.setAttribute(k, settings[k]);
if ("text" in settings)
el.textContent = settings.text; //el.innerText; For IE
return el;
}
}
//Usage:
$(function(){
$("#svg-elem").append(
createSvgElem({tagName: "rect", width: 130, height: 500, style: "fill: #000000a3;"})
)
$("#svg-elem").append(
createSvgElem({tagName: "text", x: 30, y: 30, text: "ABCD", style: "fill: red"})
)
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<svg id="svg-elem" width="200" height="200">
</svg>
With jquery you can do just that.
setting the DataType to 'text'.
$.ajax({
url: "url-to-svg.svg",
dataType : 'text'
})
.done(function(svg) {
let svg_live = $(svg);
svg_live.append('<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>');
$('#selector-id').html(svg_live);
});
Alternative 1: native js insertAdjacentHTML()
Provided you don't consider switching to native JavaScript at all ...
You could also use native javaScript method insertAdjacentHTML() for similarly convenient notation.
$("#svg")[0].insertAdjacentHTML(
"beforeEnd",
'<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'
);
$("#svg")[0] makes your jQuery object selectable in native JS.
Alternative 2: write a native js DOMParser() helper
mdn web docs: DOMParser.parseFromString()
function createSvgEl(markup) {
markup = `<svg xmlns="http://www.w3.org/2000/svg">
${markup}</svg>`;
const svgEl = new DOMParser().parseFromString(markup, "image/svg+xml")
.documentElement.children[0];
return svgEl;
}
Usage in jQuery:
$("#svgXML").append(
createSvgEl(
'<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'
)
);
Demo
// native js helper
function createSvgEl(markup) {
markup = `<svg xmlns="http://www.w3.org/2000/svg">
${markup}</svg>`;
const svgEl = new DOMParser().parseFromString(markup, "image/svg+xml")
.documentElement.children[0];
return svgEl;
}
$(document).ready(function() {
// works - but will remove existing children
$("#svg1").html(
'<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'
);
// works
// $("#svg")[0] makes your jQueryObject selectable in native JS
$("#svg")[0].insertAdjacentHTML(
"beforeEnd",
'<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'
);
$("#svgXML").append(
createSvgEl(
'<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'
)
);
// jquery still works! Vanilla doesn't harm!
$("#svgXML circle:nth-of-type(2)").attr('fill', 'orange');
//insert after()
$("#svgAfter circle").after(
createSvgEl(
'<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'
)
);
//insert after native
$("#svgAfterNative circle")[0].insertAdjacentHTML(
"afterEnd",
'<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>'
);
});
svg {
border: 1px solid red;
overflow: visible;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>Append via native js insertAdjacentHTML()</p>
<svg id="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">
<circle cx="10" cy="10" r="5" fill="green" />
</svg>
<p>Append via DOMParser() helper</p>
<svg id="svgXML" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100" width="200px" height="100px">
<circle cx="10" cy="10" r="5" fill="green" />
</svg>
<p>Append via jquery html() - will strip existing child nodes</p>
<svg id="svg1" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100" width="200px" height="100px">
<circle cx="10" cy="10" r="5" fill="green" />
</svg>
<p>Insert after existing element with jQuery after() using DOMParser() helper</p>
<svg id="svgAfter" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100" width="200px" height="100px">
<circle cx="10" cy="10" r="5" fill="green" />
</svg>
<p>Insert after existing element with native js insertAdjacentHTML()</p>
<svg id="svgAfterNative" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100" width="200px" height="100px">
<circle cx="10" cy="10" r="5" fill="green" />
</svg>
jquery's after() or before() methods will also fail to add a SVG DOM (depending on the correct namespace) element.
Using the aforementioned workarounds will fix this issue as well.
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:

More SVG questions (and Javascript) - Interactivity [moving a rectangle]

The following code is supposed to make the rectangle move when pressing the WASD keys on the keyboard. It doesn't work. The rectangle does not move, but no errors are generated. How can I make this work?
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="90%" height="90%" version="1.1"
xmlns="http://www.w3.org/2000/svg">
<script type="text/javascript">
<![CDATA[
var x;
var y;
function move()
{
x = new Number(document.getElementById("player").x);
y = new Number(document.getElementById("player").y);
switch (event.keyCode)
{
case 119:
y--;
document.getElementById("player").y=y;
break;
case 115:
y++;
document.getElementById("player").y=y;
break;
case 97:
x--;
document.getElementById("player").x=x;
break;
case 100:
x++;
document.getElementById("player").x=x;
break;
default:
}
}
document.documentElement.addEventListener("keypress", move, true);
]]>
</script>
<rect x="0" y="0" height="100%" width="100%" style="stroke-width:5; stroke:black; fill:white"></rect>
<rect x="250" y="250" id="player" height="50" width="50" style="stroke-width:1; stroke:red; fill:red"></rect>
</svg>
Your code above does not work because the x and y properties of an SVGRectElement are SVGAnimatedLength objects, representing values that can change over time.
The simplest changes to make your code 'work' are:
Change
x = new Number(document.getElementById("player").x);
to
x = new Number(document.getElementById("player").x.baseVal.value);
(and similarly for y).
Change
document.getElementById("player").x=x;
to
document.getElementById("player").x.baseVal.value=x;
(and similarly for y).
This will cause your code to work as desired. However, your code could generally be improved per my answer to your previous question.
Here is how I would write it (if you really only want to move one unit at a time):
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="90%" height="90%" version="1.1"
xmlns="http://www.w3.org/2000/svg">
<style>
#player { stroke-width:1; stroke:red; fill:red }
</style>
<rect x="250" y="250" id="player" height="50" width="50" />
<script type="text/javascript"><![CDATA[
var KEY = { w:87, a:65, s:83, d:68 };
var rect = document.getElementById('player');
var x = rect.getAttribute('x')*1,
y = rect.getAttribute('y')*1;
document.documentElement.addEventListener("keydown", function(evt){
switch (evt.keyCode){
case KEY.w: rect.y.baseVal.value = --y; break;
case KEY.s: rect.y.baseVal.value = ++y; break;
case KEY.a: rect.x.baseVal.value = --x; break;
case KEY.d: rect.x.baseVal.value = ++x; break;
}
}, false);
]]></script>
</svg>
Note that I've moved the <rect> above the <script> block so that a) the user sees the rectangle before the script starts to load, but more importantly b) so that the rectangle exists before calling getElementById(). If your script block occurs before your elements have been defined, you must instead set up your code references in response to an onload or similar event handler.

Categories