I put my two SVG scripts that I hacked on the last couple weeks into more formal JavaScript library forms so that others can reuse them.

You can check out the two libraries here.

  • SvgSnow creates the fun effect of falling and accumulating snow as can be seen during the winter months in my blog header (works in Firefox 1.5, IE+ASV and Opera 9 TP1).
  • Dragging SVG allows you to make arbitrary SVG elements draggable via the mouse (works in Firefox 1.5 and IE+ASV).


Let It Snow, Let It Snow, Let It Snow

The SvgSnow library grew out of a toy (or "one-off") script that I had specifically written for my blog header, inspired by Scott's snowstorm DHTML script.

One thing I discovered when turning my script into generally reusable library was that a lot more work was required to get something I would call barely "useful" outside of my little sandbox. I guess I'm still not accustomed to writing for reuse when designing my web scripts.

First, I had to rewrite some SVG gradient elements as JavaScript that would dynamically create those SVG elements. It would be nice to have a generic function or XSLT for that.

Next, when generalizing code to handle more scenarios than the original application it's sometimes hard (or impossible) not to incur performance penalties. I found two examples of this when rewriting my SVG Snow script into library form:

  • My original snow script allowed the snowflakes to fill and traverse the entire 880x200 image and I had hard-coded the width/height of the image into the code. Bad idea, I know. To make it more generally useful, I decided to let the user specify an arbitrary rectangle where the snow should fall and land. Thus, it's possible to fill only a portion of your SVG image (think of a window in an indoor setting). The challenge was to make sure that the flakes were not visible outside of this bounding box, which ultimately required me to constantly check the coordinates of each flake and toggle the display attribute appropriately. Of course, all this checking causes a performance penalty over the hard-coded case (where, in fact, I didn't care about flakes outside of the viewport). If I have time in a later iteration I will investigate using a clipping mask to accomplish the same effect which might improve performance.
  • The accumulating snow is accomplished by a single path element located at the bottom of the bounding box with many line segments in it. Firefox 1.5 and Opera 9 TP1 support the ability to access the path segment values directly through the SVGPathElement DOM interface (the pathLength attribute), but Adobe SVG Viewer does not yet support this attribute. As a result, in order to handle all 3 deployment configurations, I had to use a suboptimal approach of storing substrings of each path segment, then joining the substrings every time I want to update the accumulated snow. Similarly, the Adobe SVG Viewer does not support the SVGSVGElement methods suspendRedraw() and unsuspendRedraw() so I have to check whether those methods exist before attempting to call them (another minor performance hit).

In both cases, to widen the capability of the script I have to incur a performance penalty.

Anyhoo, to use the snow script it's pretty simple:

  • include a script element that references svgsnow.js
  • include a named <g> element that you want to have the snow in
  • call initSnow() with the name of the <g> element, the rectangular bounds for the snow to appear, the number of flakes (optional, defaults to 50), the speed factor (optional, defaults to 0.33) and whether snow should accumulate (optional, defaults to false)

Like so:

<svg ... viewBox="0 0 400 300"
      onload="initSnow('snowscape', 0, 0, 400, 300, 80, 1.0, true)">
   <script xlink:href="svgsnow.js" />
   <image id="background" ... />
   <g id="snowscape"/>
   <circle id="spot" ...>
</svg>

Note that the position of the <g> element defines whether the snow will be on top or underneath elements due to the SVG Painter's Model. In the above example, the snow will be on top of the background but fall behind the "spot" element.


What A Drag!

As for the Dragging SVG library, I was learning how to enable dragging for purposes of writing another simple SVG game. Dragging is one part of User Interface functionality, so it's a small step in the direction of a generally useable SVG widget. However, rather than entirely re-invent the entire wheel myself, going forward it would perhaps be wiser to stand on others' shoulders. I am peripherally aware of two formal projects related to creating SVG UI frameworks and widgets: SPARK and CGUI. It seems that Chris Peterson ported his CGUI widgets to the open source SPARK and presented his results at the SVG.Open 2005. Good for him. I hope that presentation went well.

I am eager to see development of SPARK continue past its academic phase into the browser market as a whole. The SPARK demo I tried currently only works in the Adobe SVG Viewer, but is broken in Firefox 1.5 and Opera 9 TP1. When SPARK started, only ASV was around - now it's time to get SPARK ready for other implementations (Safari is getting into the game too). Based on my "SVG Dragging" experiment (DEMO here), I suspect that with some hard work (or workarounds) it should be possible to get it working in at least Firefox 1.5. And Opera developers have told me that my drag demo works in their current development version.

SPARK seems to use the concept of tagging SVG elements with classes that are meaningful to script that can then endow those elements with various capabilites (resizing, moving, etc). I had this idea when I first wrote the toy script, but in the end, I decided to use custom attributes in my own namespace. This is a lot easier and faster than searching through all elements, then splitting the element's class attribute and searching for "draggable". To use this script to make an element draggable you:

  • include a reference to my drag namespace (http://www.codedread.com/dragsvg)
  • include a script element that references dragsvg.js
  • add drag:enable="true" to any element you want to be able to drag, where the "drag" prefix corresponds to the XML namespace mentioned above
  • To enable the drag, you call initializeDraggableElements() to enable all such elements or you can call enableDrag()/disableDrag() on an element-by-element basis

Like so:

<svg ... xmlns:drag="http://www.codedread.com/dragsvg">
   <script xlink:href="dragsvg.js" />
   <rect id="button" fill="blue" ... onclick="enableDrag(document.getElementById('spot'))" />
   <circle id="spot" fill="green" drag:enable="true" ...>
</svg>

In this example, you can click the blue rectangle button to make the green "spot" draggable.

Anyway, I'll have to get in touch with Alastair Fettes to see if the project is still being actively developed, perhaps they can reuse some of the code in dragsvg.js.


§202 · January 5, 2006 · JavaScript, Software, SVG, Technology, Web · · [Print]

2 Comments to “SVG Toy Libraries (Drag and Snow)”

  1. Instead of taking the performance hid penalty of testing for the availability of some method every tick, you could just do the test once in your init method and create a custom function you then use that runs what was available, or does nothing at all.

    var myFoo = function(){};
    if( document.Foo )
    myFoo = function(){ document.Foo(); };

    myFoo();

    In most practical cases the penalty incurred by the above test is probably small, but if you have lots of browser test branching, especially in tight loops, there is speed to gain from customizing functions to do the right thing once, and using them throughout the rest of the lifetime of the script, rather than performing the same tests over and over again.

  2. Thanks for the tip, Johan. Yep it might be useful in some cases to do what you’re suggesting. To me your technique is identical to using virtual functions or function pointers in C/C++ but my lack of understanding about browser implementations of JavaScript is severely lacking so I don’t know enough about the tradeoffs, performance-wise.