Example provided by Juliana Williams and Andreas Neumann
This tutorial introduces how one can manipulate SVG documents using ECMAScript (Javascript) and DOM (Document Object Model). The tutorial is meant to give beginners an easy start, it is not intended to be a complete reference. It does not deal with all of the available methods. For more information see the resources below.
The DOM is a language-neutral API (Application Program Interface) that allows any programming language to access, manipulate, create, and delete elements and the document tree. DOM manipulations work the same way in all W3C/XML documents and are not SVG specific. If you know how to manipulate HTML documents using ECMAScript and the DOM, it will be easy for you to manipulate SVG documents. ECMAScript is the most popular scripting language in web browsers and SVG Viewers, hence we use it for our tutorials.
The following code shows a very simple SVG document containing only a rectangle and a text element.
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "https://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> <svg xmlns="https://www.w3.org/2000/svg" xmlns:xlink="https://www.w3.org/1999/xlink" width="300" height="300"> <g id="firstGroup"> <rect id="myBlueRect" width="100" height="50" x="40" y="20" fill="blue" /> <text x="40" y="100">This is a basic SVG document!</text> </g> </svg>
Link to example 1 (basic_doc_tree.svg) - in a separate window)
The above example contains a well-formed and valid SVG document tree. The whole document can be referenced by the ECMAScript using the document object. The SVG element is the root element of the document and can be referenced using document.documentElement. Note that the group and the rectangle have an id attribute that can be used to directly addresss an element. These id's need to be unique. A strict SVG viewer or validating XML parser would show an error message if the id's in a document weren't unique.
The next section will teach you how to access individual elements and get their attribute values.
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "https://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> <svg xmlns="https://www.w3.org/2000/svg" xmlns:xlink="https://www.w3.org/1999/xlink" width="300" height="300"> <script type="text/ecmascript"> <![CDATA[ function showRectColor() { alert(document.getElementById("myBlueRect").getAttributeNS(null,"fill")); } function showRectArea(evt) { var width = parseFloat(evt.target.getAttributeNS(null,"width")); var height = parseFloat(evt.target.getAttributeNS(null,"height")); alert("The rectangle area is: " + (width * height)); } function showRootChildrenNr() { alert("Nr of Children: "+document.documentElement.childNodes.length); } ]]> </script> <g id="firstGroup"> <rect id="myBlueRect" width="100" height="50" x="40" y="20" fill="blue" onclick="showRectArea(evt)"/> <text x="40" y="100" onclick="showRectColor()">Click on this text to show rectangle color.</text> <text x="40" y="130">Click on rectangle to show rectangle area.</text> <text x="40" y="160" onclick="showRootChildrenNr()">Click on this text to show the number of child <tspan x="40" dy="20">elements of the root element.</tspan></text> </g> </svg>
Link to example 2 (get_reference_attribute.svg) - in a separate window)
The above example shows three methods on how to make a reference to an element:
After creating the reference to an element, the attributes can be queried using the method element.getAttributeNS(). This method returns the value of an attribute, but the result is always a string value. If you want to receive a number value from an attribute you need to call the function parseFloat or parseInt as shown in the function showRectArea(evt). Although the method element.getAttribute() would work as well, it is recomended to use the method element.getAttributeNS(), because it also works in multi-namespace documents. This method takes two arguments: the namespace name and the attribute name. If the host namespace is SVG you can use null instead of the full namespace definition, as the method automatically replaces the null with the host namespace. The functions showRectColor() and showRectArea() demonstrate the usage of the element.getAttributeNS() method.
The function showRootChildrenNr() returns the number of child nodes in the root element. childNodes is an array containing references to all child elements of an element. childNodes.length returns the number of elements in the array. Batik and the Adobe SVG viewer return a different length of the .childNodes array. This is because Batik does not count the whitespace notes between the real elements, while ASV does. As a result, Batik will return 2 (the script element and the group element) while ASV will return 5: the first child is the line break and the following spaces, the second is the script element, the third again a line break and spaces, the fourth element is the group with the id "firstGroup" and the last element is again a line break.
The following will show how to set attributes of individual elements.
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "https://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> <svg xmlns="https://www.w3.org/2000/svg" xmlns:xlink="https://www.w3.org/1999/xlink" width="300" height="300"> <script type="text/ecmascript"> <![CDATA[ function changeRectColor(evt) { var red = Math.round(Math.random() * 255); var green = Math.round(Math.random() * 255); var blue = Math.round(Math.random() * 255); evt.target.setAttributeNS(null,"fill","rgb("+ red +","+ green+","+blue+")"); } ]]> </script> <g id="firstGroup"> <rect id="myBlueRect" width="100" height="50" x="40" y="20" fill="blue" onclick="changeRectColor(evt)"/> <text x="40" y="100">Click on rectangle to change it's color.</text> </g> </svg>
Link to example 3 (set_attributes.svg) - in a separate window)
Setting attributes is very similar to getting attributes. Once the elements are referenced, their method setAttributeNS() can be called. Three arguments are needed: the namespace (in this example null), the attribute name and the attribute value. To calculate random colors, the random generator (a method of the Math object) is used. This method generates a value between zero and one. As the color range is between zero and 255, it is necessary to multiply the randomly generated value by 255. The three random values form the shown RGB fill color of the rectangle.
This section shows how to check for existing attributes and how to remove them.
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "https://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> <svg xmlns="https://www.w3.org/2000/svg" xmlns:xlink="https://www.w3.org/1999/xlink" width="300" height="300"> <script type="text/ecmascript"><![CDATA[ function removeFill(evt) { var element = evt.target; if (element.hasAttributeNS(null,"fill")) { element.removeAttributeNS(null,"fill"); } else { alert("This element doesn't have a fill attribute."); } } ]]></script> <g id="firstGroup"> <rect width="70" height="50" x="40" y="5" fill="blue" onclick="removeFill(evt)"/> <rect width="70" height="50" x="40" y="65" fill="blue" onclick="removeFill(evt)"/> <rect width="70" height="50" x="40" y="125" fill="blue" onclick="removeFill(evt)"/> <rect width="70" height="50" x="40" y="185" fill="blue" onclick="removeFill(evt)"/> <rect width="70" height="50" x="40" y="245" fill="blue" onclick="removeFill(evt)"/> <text x="150" y="30">Click on rectangle<tspan x="150" dy="15">to remove it's color.</tspan></text> </g> </svg>
Link to example 4 (check_remove_attrib.svg) - in a separate window)
To check for the existance of an attribute within an element, the method element.hasAttributeNS() can be used. The arguments are: namespace (null) and attribute name. The method returns true if the attribute exists and false if it doesn't. The method removeAttributeNS() removes an existing attribute. The arguments are: namespace (null) and attribute name. If the removed attribute is required it will receive the default value as specified in the SVG specification. This is the reason why the rectangles in this example turn black. Here, the attribute is only removed if it exists and if it doesn't an error message appears.
This section will illustrate how to make references to children, parents and siblings.
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "https://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> <svg xmlns="https://www.w3.org/2000/svg" xmlns:xlink="https://www.w3.org/1999/xlink" width="300" height="100"> <script type="text/ecmascript"><![CDATA[ function showContentAndRelatives(evt) { //get reference to text element var textElement = document.getElementById("myText"); //get reference to parent of element var parent = textElement.parentNode; alert("the parent node has id '"+parent.getAttributeNS(null,'id')+"'\nNodename is '" +parent.nodeName+"'"); //get a reference to the first child of the element "myText" var child = textElement.firstChild; //loop over all childs while (child != null) { //see if child is a tspan and has child nodes if (child.nodeName == "tspan" && child.hasChildNodes()) { //see if firstChild is of nodeType "text" if (child.firstChild.nodeType == 3) { alert("child's text content="+child.firstChild.nodeValue); } } child = child.nextSibling; } alert("the content of the second tspan Child is: "+textElement.childNodes.item(1).firstChild.nodeValue); } ]]></script> <g id="firstGroup"> <text id="myText" x="50" y="30" onclick="showContentAndRelatives(evt)"> <tspan>Click on text</tspan> <tspan x="50" dy="15">to get parent node data</tspan> <tspan x="50" dy="15">and to see the text node values </tspan> <tspan x="50" dy="15">of each line</tspan> </text> </g> </svg>
Link to example 5 (parent_child_sibling_references.svg) - in a separate window)
In this example the property element.parentNode to get a reference to the parent node of an element is demonstrated. The id and nodename of the parent are alerted to prove that there is access to the parent node.
Next the use of the element.firstChild property is shown, which gives a reference to the first child of an element. Using that reference, a loop over all children of the text node is made until the child reference returns a null value. At the end of the loop, the property child.nextSibling returns the node reference to the next sibling of the child element. Within the loop it is tested whether the child.nodeName property returns "tspan" and if the child has children at all (using the child.hasChildNodes property). This is necessary because theoretically there could be other children as well. Specifically one also gets references to whitespaces (such as line feeds or blanks) between elements. Within that test it is tested if the child is of nodeType "3" which is a text node, in this case the content of the <tspan/> element.
Following is a table of nodeTypes with their corresponding nodeTypeString in XML:
1 | ELEMENT_NODE |
2 | ATTRIBUTE_NODE |
3 | TEXT_NODE |
4 | CDATA_SECTION_NODE |
5 | ENTITY_REFERENCE_NODE |
6 | ENTITY_NODE |
7 | PROCESSING_INSTRUCTION_NODE |
8 | COMMENT_NODE |
9 | DOCUMENT_NODE |
10 | DOCUMENT_TYPE_NODE |
11 | DOCUMENT_FRAGMENT_NODE |
12 | NOTATION_NODE |
In the final alert() the use of accessing the nth child of an element, using element.childNodes.item(n) is demonstrated. In this case the content of the second <tspan /> element is alerted. The reason why we use the index of 2 in this case, is because itme 0 refers to the first <tspan/> element, item 1 is a white space and item 2 is the second <tspan/> element. Note the use of parenthesis instead of brackets!
Now it will be shown how to create new elements.
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "https://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> <svg xmlns="https://www.w3.org/2000/svg" xmlns:xlink="https://www.w3.org/1999/xlink" width="300" height="300"> <script type="text/ecmascript"><![CDATA[ var svgNS = "https://www.w3.org/2000/svg"; var xlinkNS = "https://www.w3.org/1999/xlink"; function createRect() { var newRect = document.createElementNS(svgNS,"rect"); newRect.setAttributeNS(null,"width",Math.random() * 100); newRect.setAttributeNS(null,"height",Math.random() * 100); newRect.setAttributeNS(null,"x",Math.random() * 250); newRect.setAttributeNS(null,"y",Math.random() * 180 + 60); newRect.setAttributeNS(null,"fill-opacity",Math.random()); var red = Math.round(Math.random() * 255); var green = Math.round(Math.random() * 255); var blue = Math.round(Math.random() * 255); newRect.setAttributeNS(null,"fill","rgb("+ red +","+ green+","+blue+")"); document.getElementById("firstGroup").appendChild(newRect); } function createText() { var newText = document.createElementNS(svgNS,"text"); newText.setAttributeNS(null,"x",Math.random() * 200 + 50); newText.setAttributeNS(null,"y",Math.random() * 180 + 60); newText.setAttributeNS(null,"font-size","13px"); newText.setAttributeNS(null,"text-anchor","middle"); newText.setAttributeNS(null,"fill-opacity",Math.random()); var red = Math.round(Math.random() * 255); var green = Math.round(Math.random() * 255); var blue = Math.round(Math.random() * 255); newText.setAttributeNS(null,"fill","rgb("+ red +","+ green+","+blue+")"); var textNode = document.createTextNode("a new text"); newText.appendChild(textNode); document.getElementById("firstGroup").appendChild(newText); } function createUseElement() { var newUseEl = document.createElementNS(svgNS,"use"); newUseEl.setAttributeNS(null,"x",Math.random() * 200 + 50); newUseEl.setAttributeNS(null,"y",Math.random() * 180 + 80); newUseEl.setAttributeNS(xlinkNS,"href","#mySymbol"); newUseEl.setAttributeNS(null,"fill-opacity",Math.random()); document.getElementById("firstGroup").appendChild(newUseEl); } ]]></script> <defs> <symbol id="mySymbol" overflow="visible"> <rect id="redRect" fill="red" stroke="none" x="-10" y="-10" width="20" height="20" /> <use transform="rotate(45)" xlink:href="#redRect" /> </symbol> </defs> <g id="firstGroup"> <text x="20" y="30" onclick="createRect()" font-size=+13px">Click on this text to create a new rectangle.</text> <text x="20" y="45" onclick="createText()" font-size=+13px">Click on this text to create a new text.</text> <text x="20" y="60" onclick="createUseElement()" font-size=+13px">Click on this text to create a new use element.</text> </g> </svg>
Link to example 6 (create_elements.svg) - in a separate window)
In order to create a new element the method document.createElementNS() is used. The arguments are: namespace (in this case it needs to be explicitly set to the correct namespace url, here it is defined in the variable svgNS) and element name. The element reference is defined in a new variable, in this example "newRect". This reference is later used to set attribute values as described in the above code. Usually a range of attributes need to be defined. Finally, the newly created element needs to be appended to an existing group or element in the document tree. In this example the new rectangles are appended to the group with the id "firstGroup". The created elements are therefore siblings of the text element and are appended after it.
Creating text elements is different: you have to create a text-node using the document.createTextNode("yourText") method, that is later appended to the text element that you dynamically created as well. See above function createText() for an example.
Creating <use /> elements is different as well. You have to explictly use the xlink namespace when setting the href attribute. See function createUseElement() listed above as an example.
It is obvious that creating elements like in the method above, requires a lot of javascript code. One alternative would be to write a generic javascript function or object where you pass the element name and all attribute/value pairs and the function would create the elements. This would reduce the number of lines in the javascript if you have to create lots of different elements.
Another alternative is to build strings containing the svg code of an element and use the method parseXML() to append it to a child. The code below illustrates how you would use that method for creating a new rectangle:
var newRect = '<rect x="'+Math.random()*250+"' y="'+Math.random()*200+60+'" width="'Math.random()*100'" height="'Math.random()*100'" fill="red" stroke="blue"/>'; document.getElementById("firstGroup").appendChild(parseXML(newRect,document));
I personally did not do any performance tests to find out which method is faster, but other people told me that the parseXML() method is faster when creating a lot of elements.
The following shows how to remove and replace elements.
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "https://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> <svg xmlns="https://www.w3.org/2000/svg" xmlns:xlink="https://www.w3.org/1999/xlink" width="300" height="300"> <script type="text/ecmascript"><![CDATA[ var svgNS = "https://www.w3.org/2000/svg"; function removeElement(evt) { var element = evt.target; element.parentNode.removeChild(element); } function replaceRect(evt) { var rect = evt.target; //determine center for new circle var centerX = parseFloat(rect.getAttributeNS(null,"x")) + parseFloat(rect.getAttributeNS(null,"width")) / 2; var centerY = parseFloat(rect.getAttributeNS(null,"y")) + parseFloat(rect.getAttributeNS(null,"height")) / 2; //create a new circle element and set the attributes var circle = document.createElementNS(svgNS,"circle"); circle.setAttributeNS(null,"cx",centerX); circle.setAttributeNS(null,"cy",centerY); circle.setAttributeNS(null,"r",parseFloat(rect.getAttributeNS(null,"height")) / 2); circle.setAttributeNS(null,"fill","blue"); circle.addEventListener("click",removeElement,false); rect.parentNode.replaceChild(circle,rect); } ]]></script> <g id="firstGroup"> <rect width="70" height="50" x="40" y="5" fill="blue" onclick="replaceRect(evt)"/> <rect width="70" height="50" x="40" y="65" fill="blue" onclick="replaceRect(evt)"/> <rect width="70" height="50" x="40" y="125" fill="blue" onclick="replaceRect(evt)"/> <rect width="70" height="50" x="40" y="185" fill="blue" onclick="replaceRect(evt)"/> <rect width="70" height="50" x="40" y="245" fill="blue" onclick="replaceRect(evt)"/> <text x="150" y="30">Click on rectangle<tspan x="150" dy="15">to exchange it with a circle,</tspan> <tspan x="150" dy="15">Click on Circle to remove it.</tspan> </text> </g> </svg>
Link to example 7 (remove_or_replace_elements.svg) - in a separate window)
In order to remove an element it is necessary to know its parent element. The property parentNode makes a reference to the parent of an element. The method element.removeChild(element) removes a child element. The argument is a reference to the element to be removed.
To replace an element with another element two node references are needed. Of course one can create a new reference by creating a new element, as it is the case in this example. The method .replaceChild(newChild,oldChild) needs two arguments: the new reference to be inserted and the old reference to be replaced.
The method .addEventListener(type,listener,useCapture) allows one to dynamically add a new event listener to an element. The first argument is the type (e.g. "click"), the second argument is the event listener function or object (note that the event listener automatically passes the evt object to the function or object that implements the EventListener interface), the third argument indicates how the event is traversing the document tree (bubbling). Usually, one uses false here. Event Handling, however, will be a separate tutorial to be written at a later stage.
The following shows how to clone/duplicate elements.
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "https://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> <svg xmlns="https://www.w3.org/2000/svg" xmlns:xlink="https://www.w3.org/1999/xlink" width="300" height="300"> <script type="text/ecmascript"><![CDATA[ function cloneRect(evt) { var cloneElement = evt.target.cloneNode(false); var newx = 25 + Math.random() * 200; var newy = 70 + Math.random() * 150; cloneElement.setAttributeNS(null,"x",newx); cloneElement.setAttributeNS(null,"y",newy); document.getElementById("firstGroup").appendChild(cloneElement); } ]]></script> <defs> <linearGradient id="MyGradient"> <stop offset="5%" stop-color="red" /> <stop offset="95%" stop-color="yellow" /> </linearGradient> </defs> <g id="firstGroup"> <rect width="70" height="50" x="40" y="5" fill="url(#MyGradient)" stroke="blue" stroke-width="2" onclick="cloneRect(evt)"/> <text x="150" y="30">Click on rectangle<tspan x="150" dy="15">to clone it.</tspan> </text> </g> </svg>
Link to example 8 (clone_elements.svg) - in a separate window)
The advantage of cloning elements instead of creating them from scratch, is that existing attributes can be re-used and if a lot of attributes are shared, make the code shorter. In order to clone an element, a node reference is needed. From this node reference the method .cloneNode(false) can be called. A new element reference is returned that can be manipulated like any other element reference, as described above, e.g. using the method .setAttributeNS(). In this case the random generator to change the x and y values of the cloned element is used. All the other attributes (such as fill, onclick, stroke, etc.) remain the same. The argument false in the method .cloneNode() means that only the element and it's attributes are cloned, but not any children. If this argument is set to true, a copy including the subtree under the specified node is returned. Finally, the cloned element needs to be appended again somewhere in the document tree, using e.g. the .appendChild() method. Remark: if an element that has a unique id as an attribute is cloned, be sure to either remove that id, or make it unique again using .setAttributeNS(null,"id",newUniqueId)
This example shows how to re-order elements.
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "https://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> <svg xmlns="https://www.w3.org/2000/svg" xmlns:xlink="https://www.w3.org/1999/xlink" width="300" height="300"> <script type="text/ecmascript"><![CDATA[ function putToBottom(evt) { //get element reference var element = evt.target; //insert selected element before the first child //first test if it isn't already the first Child if (element.previousSibling) { element.parentNode.insertBefore(element,element.parentNode.firstChild); } } function putToTop(evt) { //get node reference var element = evt.target; //appendChild after the last child element.parentNode.appendChild(element); } ]]></script> <g> <rect width="70" height="50" x="10" y="5" fill="blue" onclick="putToBottom(evt)"/> <rect width="70" height="50" x="20" y="25" fill="red" onclick="putToBottom(evt)"/> <rect width="70" height="50" x="30" y="45" fill="yellow" onclick="putToBottom(evt)"/> <rect width="70" height="50" x="40" y="65" fill="orange" onclick="putToBottom(evt)"/> <rect width="70" height="50" x="50" y="85" fill="green" onclick="putToBottom(evt)"/> <text x="120" y="30">Click on a rectangle to put it <tspan x="120" dy="15">to the bottom of all rectangles.</tspan></text> </g> <g transform="translate(0,150)"> <rect width="70" height="50" x="10" y="5" fill="blue" onclick="putToTop(evt)"/> <rect width="70" height="50" x="20" y="25" fill="red" onclick="putToTop(evt)"/> <rect width="70" height="50" x="30" y="45" fill="yellow" onclick="putToTop(evt)"/> <rect width="70" height="50" x="40" y="65" fill="orange" onclick="putToTop(evt)"/> <rect width="70" height="50" x="50" y="85" fill="green" onclick="putToTop(evt)"/> <text x="120" y="30">Click on a rectangle to put it <tspan x="120" dy="15">on the top of all rectangles.</tspan></text> </g> </svg>
Link to example 9 (reorder_elements.svg) - in a separate window)
In some cases it is necessary to re-order elements. The methods .insertBefore() and .appendChild() do this. .insertBefore(newChild,refChild) needs two arguments, the first is the reference to the element to be inserted before the other element and the second is the reference to the other element. In our example we first test, if the element is not already the first Child. element.previousSibling would return false if the element is already the first child of the group. element.appendChild(newChild) has already been demonstrated. It appends a child reference to another element or group. The new element is inserted after the last child element of the parent. As an argument it needs the child reference to be inserted or moved.
Last modified:
Tuesday, 10-Dec-2019 21:34:02 CET
© carto:net (andreas neumann & andré m. winter) original URL for reference: https://old.carto.net/papers/svg/manipulating_svg_with_dom_ecmascript/index.shtml |