Menu

SVG Tips and Tricks, Part One

March 27, 2002

Antoine Quint

Introduction

The previous installments of this column discussed the two major techniques at the core of interactive SVG: SVG animation and DOM scripting. We saw how the powerful declarative syntax adapted from SMIL brought life to SVG documents, and how SVG's DOM goes further than XML's core DOM. This month's column, rather than a focused exploration of a particular SVG topic, will examine a few helpful tips and tricks. First, I'll introduce the viewBox attribute for zooming purposes, then explain how to use the SMIL DOM interfaces for remote animation startups, and, last, I'll conclude with a closer look at DOM Events. At the end, you will understand the tricks used and abused in the interactive tree map companion demo.

Before we turn to specifics, I should tell you a bit about the demo. The SVG code of the tree map was actually generated by XSLT from an XML file. The aim was to construct a mechanism to represent an XML file's hierarchy in SVG and be able to browse it in an interactive way. So we are dealing with a nice little tree app here. If you look at the SVG code you'll see that there is still a strong depth structure here too. The idea is that each time we met a node in the XML file, we would create a rectangle and a text label. If the node had a child, then we would process the subtree according to the same rule. By the way, I nicked this idea from Niklas Gustavsson, who's done a fair bit of similar experimentation.

Fiddling With viewBox

One of the obvious advantages of vector graphics is that they can scale for crisp viewing in a variety of situations. You can apply any of SVG's transformations and filters (or any combination) to your graphics and always get a clean rendering. Suppose you have an SVG world map. You might allow viewers to select a country to see it in more detail by displaying it in the largest factor possible. How do you do this?

A first approach would be to take the group of shapes that represent the map and apply the correct SVG scale and translate transformations to it. However, it would be a bit of a headache to compute the correct transformations. Wouldn't it be nice to have a mechanism that would allow you to specify an area to be displayed at maximum zoom factor? Well, there is one, the viewBox attribute.

A viewBox is, according to the SVG recommendation, " a rectangle in user space which should be mapped to the bounds of the viewport established by the given element". So if your map had a country with a bounding box of (100,100,400,200), then to have it fully zoomed you would set the viewBox attribute to the same values. The SVG implementation computes, from the rectangle coordinates you specified, the correct transformations to apply to the graphics, which saves you a fair bit of head-scratching. The viewBox can be used on any shape that defines a new viewport (and in a few more cases; the SVG recommendation has the details). And it gets better.

What happens when you want to zoom an object whose ratio does not match your composition's size? The viewBox has a companion attribute, preserveAspectRatio, which allows you to specify how the Viewer should handle zooming of shapes of any type of ratio. But the best thing about the viewBox is that it can be animated. If you look at the tree map demo, you'll see we have used that feature to animate the top-level (outermost) <svg> element. Animating a viewBox is not different than the way we animated the y attribute of the cubes in the first column. A simple viewBox animation would look like the following.


<svg width="600" height="400" viewBox="0 0 600 400">

  <circle cx="100" cy="100" r="90" style="fill: red" />

  <rect x="250" y="180" width="300" height="200" style="fill: green" />

  <animate attributeName="viewBox" begin="1s" dur="1s"

           values="0 0 600 400; 250 180 300 200" fill="freeze" />

</svg>

      

View this example as SVG

What we've done is specify the original viewBox at what it would have been by default (starting at the origin with composition's width and height). Then we added an <animate> element that would ask the Viewer to smoothly transition between the original viewBox and the one that will fit the green rectangle (250 180 300 200). In our tree map demo, we have a more subtle usage of the <animate> element since we script it a fair bit

Scripting Animations

Back in the days when we were animating cubes, we saw that SVG, by borrowing from SMIL Animation, allowed us to define animations declaratively using a simple yet powerful XML syntax. Then we saw that when doing drag-and-drop that SVG had full DOM Level 2 capabilities as well as some extensions of its own (the SVG DOM) that opened a new world of possibilities for scripting. Now, how could we mix these two sides of interactive SVG to extend our skills further?

Since the animation in SVG is done through an XML model, we can actually use the simple DOM scripting techniques to start making funky things already. In our tree map demo, since there is never more than one animation at a time, we use a single <animate> element for every single animation. However, with bare SVG, we can't make an <animate> element compute the bounding box of whatever element has received the mousedown event and pass it to the values attribute, then launch itself. We need a little more programmatic power here, which is where DOM scripting comes into play. Here is what our single <animate> looks like originally (as placed as the immediate child of our outermost <svg> element):


<animate id="anim" attributeName="viewBox" begin="undefined" 

          dur="0.75s" values="" keyTimes="0; 1" keySplines="0 .75 0.25 1" 

          calcMode="spline" fill="freeze" />

We have also placed event listeners on our squares in the tree map so that they would call the startAnimation() method that we have coded. It is within this function that we access our <animate> element and update its values. Basically, all we do is get the bounding box of the object we have just clicked and add it to the values attribute after the current viewBox. We actually have a method printViewBox() that gets an object's bounding box and returns it as a nicely formatted string. So updating the values is easy enough, we don't get to use anything fancier than the DOM 2 Core. But then near the end of the startAnimation() method we have this funny line:

anim.beginElement();      

   Related Reading

SVG Essentials

SVG Essentials

SVG animation borrows another bit off SMIL animation: its ElementTimeControl DOM interface. Yet another DOM interface, but don't panic, this one is small (only four methods) and truly essential. It allows you to start and stop SVG animations from your script without tracking the current time or anything messy like that. It is fully integrated with the SVG animation model and respects settings from the restart and fill attributes. You can also set timed offsets when you want to start or end an animation. In order to call these methods on an animation element, it has to have its begin or end attributes set to undefined. Calling beginElement() on our <animate> element will start the animated transition right away, with no offset. Using DOM 2 Core and the SVG DOM we update our animation's parameters and launch it rather easily.

Playing With Events

There are a few remaining subtleties in our tree map. You might have noticed that there are actually two speeds at which transition animations can be played. If you click on a shape with your left button, the animation will last 0.75 seconds. If you right-click on the shape, it will animate twice as fast and not show the Adobe SVG Viewer menu (if you are using the Adobe SVG Viewer). How did we do that? With a little help from the DOM Level 2 Events. We've already played a bit with events when working on drag-and-drop, which included inspecting the events to get the mouse coordinates. Now we can introduce a couple more subtleties. Consider this piece of code:

function getSpeed (evt) {

  var speed = 0.75;

  if(evt.button == 2) {

    evt.preventDefault();

    speed /= 2;

  }

  return speed;

}      

Also in Sacré SVG

Big Lists in Small Spaces

SVG At the Movies

Mobile SVG

SVG and Typography: Animation

Going Mobile With SVG: Standards

The getSpeed() function is called when we update the dur attribute of our animation within our startAnimation() function. You see that it is passed an evt event from startAnimation(), and evt will save the day again by giving us the information we need. Since our event was originally triggered by clicking on a square, it is of type MouseEvent. Its button property informs us of what button the user pressed, returning 0 for left, 1 for middle and, 2 for right. All we need to check is for evt.button to be equal to 2, and in that case divide the normal speed by 2. We also need to make sure that the usual context menu will not appear since this would considerably clog our view and lessen the effect of our transition animation. Then we just have to call the preventDefault() method as defined on the Event interface that MouseEvent inherits from. This method will make sure that "any default action associated with the event will not occur".

Wrapping It All Up

That concludes our tips and tricks session. I hope it wasn't too confusing. I take care to read and respond to your comments so I can improve this column; please do not hesitate to drop me a line.