Prerequisites:
David Dailey, An SVG Primer for Today’s Browsers:
Be able to use JavaScript in combination with SVG to:
It should be obvious by now that SVG is pretty powerful. Graphical elements “know where they are” (we don’t have to keep redrawing them when the scene changes); they can be animated declaratively; they can even respond to events coming from other graphical objects.
What sort of things can’t we do with SVG, without resorting to scripting? Or what would at least be very difficult do manage with just plain SVG?
<script> Element in SVGSVG, as well as HTML, documents can contain <script> tags. In SVG they are a little different:
<script xlink:href="SCRIPT-URL"></script>for an external script (using the xlink:href instead of src attribute); or
<script>
<![CDATA[
...
]]>
</script>for an inline script. The CDATA section prevents our script from being parsed as XML. (Should we have been doing this also with HTML scripts to control the canvas?)
Alternatively, you can have a script in your HTML (not in the SVG), and it can still access the SVG subtree of the document.
Any SVG graphical element (e.g., <rect> or <g>) can have attributes that specify an event handling function or script. Some of these attributes are onfocusin, onfocusout, onactivate, onclick, onmousedown, onmouseup, onmouseover, onmousemove, and onmouseout. Animation elements can have onbegin, onend, and onrepeat. There are also document events. SVG 1.1 does not include keyboard events, although these are planned for a later specification.1 For details, see SVG 1.1, section 16.2, Complete list of supported events and SVG 1.1, section 18.4, Event attributes.
For generality, allowing non-mouse devices as well as mice, onactivate may be preferable to onclick.
Event objects (we will use the conventional variable evt) have a target (evt.target), the element which triggered the event (e.g., the rect that you mouse over). In case of complex (grouped) objects, you may use evt.currentTarget to find the group element to which the listener is assigned, where evt.target will be the component (e.g., rect or ellipse) within the group that actually triggered the event.
Mouse events have evt.clientX and evt.clientY. It looks like clientX and clientY are relative to the SVG element, as long as the script is an SVG script element instead of an HTML script element.
The evt.type is the type of event (e.g., "mouseover").
Recall that namespaces in XML, like modules in Python and packages in Java, allow us to use the same name in different contexts without conflict. Because XML is not so much a language as a framework for making languages, most XML “applications” (i.e., languages based on XML) define and use a namespace. An XML document can have a default namespace and can also use prefixes to represent explicit namespaces. For more on this, see INFO I308 notes on XML namespaces.
Any HTML, SVG, or other XML document has (or in the case of HTML, should have) a tree structure consisting of a root node with subtrees. For more information on the DOM, see INFO I308 notes on the Document Object Model.
To make scripted changes in our document, we manipulate this tree.
Useful methods (use NS variants for namespace-qualified elements and attributes; also, null can be used if there is no namespace):
document.getElementById("ID") // returns a NODE
node.getElementsByTagNameNS("NAMESPACE", "TAGNAME") // returns a sequence of nodes
document.createElementNS("NAMESPACE", "TAGNAME") // returns a NODE
node.getAttribute("ATTRNAME") // returns a string
node.getAttributeNS("NAMESPACE", "ATTRNAME") // returns a string
node.setAttribute("ATTRNAME", "ATTRVALUE")
node.setAttributeNS("NAMESPACE", "ATTRNAME", "ATTRVALUE");
node.appendChild(CHILDNODE) // inserts CHILDNODE into the subtree of node
// as its last child
node.removeChild(CHILDNODE) // removes CHILDNODE from the subtree of nodeNote: SVG elements are in the SVG namespace, "http://www.w3.org/2000/svg", but SVG attributes without a prefix are in no namespace. Therefore, to get an attribute value, either use the no-namespace version node.getAttribute("ATTRNAME"), or use null for the namespaces in node.getAttributeNS(null, "ATTRNAME"); similarly for setting attribute values.
Useful attributes for navigating through the DOM, from some node:
node.parentNode
node.firstChild
node.nextSibling
node.childNodes.length
node.childNodes.item(INDEX) // 0 <= INDEX < length
node.attributes // itemlist of objects having nodeName and nodeValue properties
node.nodeValue // e.g., the string value of a text nodeRandom gradients. Click on an ellipse to change its gradient stops to random colors.
Here is the solution.
Comments:
randomHSLA returns a random color, with the help of the uniform function.change function is the callback for a click on the ellipse. It sets the two stops of the gradient to random colors. Its unused argument, evt, represents the event.setAttribute to set the stop colors; or if we use setAttributeNS, specify null as the namespace.none. This does not work well in Chromium.A particle system. A stream of orange-yellow particles flows from left to right, like fire blown by a dragon. This involves inserting animated ellipse elements, representing the particles, into the DOM.
Here is the solution.
Comments:
The script creates 200 elliptical “particles,” which more or less resemble this element:
<ellipse rx="18" ry="3" fill="orange">
<animateTransform attributeName="transform" type="translate"
values="50 300; 850 300" dur="3.6s"
additive="sum" repeatCount="indefinite"/>
</ellipse>However, we want to make slight random variations in the color and in the duration and trajectory (which together determine the particle’s speed). Specifying repeatCount="indefinite" causes the particle to “recycle” from the beginning.
We are going to be creating a lot of these elements, so it’s helpful to start with an element function. This function uses the document.createElementNS method to create an element with a specified tag, then loops through the specified attribute properties setting them with elt.setAttribute.
The aniTrans function calls element to create an animateTransform node.
Actually, we’re going to create not just a bunch of elements, but a bunch of little trees in which an ellipse element is the root and an animateTransform element is the single child. So there’s a helpful tree function which is given the root node and a list of children as arguments.
The particle function uses tree to create this little pair, also introducing a slight randomness into the y coordinates.
The startParticles function executes a loop which creates 200 particles all at about the same time. But we want the animations to start with incremental delays of 25 milliseconds, so that they all move across the drawing one after another, instead of all in the same place. The appendChild method inserts the particle into the SVG document tree.
In this script, the randomHSLA produces random colors from a narrower range: the hue is orange to yellow, the colors are fairly saturated, moderately light, and somewhat transparent.
Since the particles are being recycled by having an indefinite repeat count, there is no need to ever remove them from the DOM. The animation goes on forever.
Here are some more methods that are sometimes useful:
node.getBBox() // rectangular bounding box of the graphical element
// for <path> element nodes
pathnode.getTotalLength() // number
pathnode.getPointAtLength(DISTANCE) // point with x, y properties
// for <animate>, <animateTransform>, <animateMotion> element nodes
// with begin="indefinite" to inhibit starting before scripted begin
node.beginElement(); // starts animation
node.endElement(); // stops animation
// Methods of the SVG element; available as top-level functions
// within an SVG script, but must be called as methods of the
// SVG node in HTML scripts
// Get and set the time in seconds relative to the start of
// the SVG element, which is the 0 time for beginning animations.
svg.getCurrentTime();
svg.setCurrentTime(secs);Modify the particle system to start on a mouse click. The particle stream is activated in short bursts by mouse clicks.
Here is the solution.
Comments:
Some care is needed to start the animations relative to the time of the mouse click, instead of the time the drawing loads. If the particles started at times 10, 20, 30, etc., then they would already be out of sight unless the mouse click came soon after the drawing loaded. The script uses the SVG getCurrentTime method to calculate the starting time for each particle (1000 * current time + offset, where offset is 10, 20, 30, etc.).
The particles are removed from the DOM after they leave the viewing area, using the removeChild method. It would be great to be able to do this in response to an animation end event, but this doesn’t work in Chromium; so the script sets a timeout to make this happen—remember timeouts from canvas animation scripting?
Since animations can be CPU-intensive (up to nearly 100%), offering controls to pause and start the animation is courteous.
// stop or restart all animations
svg.pauseAnimations();
svg.unpauseAnimations();
svg.animationsPaused(); // boolean resultMultiple animations with pause and restart controls in HTML. This is like Example 19-03, but with controls.
In fact, the SVG2 draft still says, “the SVG specification does not provide a key event set. An event set designed for use with keyboard input devices will be included in a later version of the DOM and SVG specifications. SVG 2, Chapter 16, Interactivity What can we do, then—set the event handler on an enclosing HTML element, maybe a div or the whole document?↩︎