In the midst of building out some maps for election night (more about which will be posted later, probably after election night), I came across the great conundrum of vector-based images.

As anyone who’s ever worked with an image map before knows, making clickable elements out of raster-based images (JPGs, PNGs) is tedious and ultimately somewhat unfulfilling, as even if you manage to craft the most perfectly intricate image map, you still can’t easily change the colors of any of your designated areas with ease.

Vector graphics (specifically, SVG) are supposed to eliminate all these woes, as are things such as the HTML5 Canvas element and numerous other things. The problem is, as ever, browser compatibility, and while some libraries and plugins have been written, they’re either far more (RaphaelJS) or less (jQuerySVG) than I need.

All I really need are a few features:

• Change the fill/stroke of paths
• Animate those changes
• Resize the whole thing (either dynamically or one time, for our non-responsive CMS)

This, as it turns out, is exceedingly difficult.

For starters, SVG does not accept the traditional jQuery class functions ($.addClass, $.removeClass, etc.), but rather requires direct element manipulation ($.attr('class','className');). When you consider the fact you might need to add a class to an extant value or subtract one of three, it’s a little rough.

Screen Shot 2014-10-22 at 4.18.44 PM

Rather than background, SVG uses “fill” as its property. Naturally, fades and transitions available via jQuery don’t work on this, either. Luckily for us, though, SVG does work with CSS transitions on the fill (and stroke) property.

Screen Shot 2014-10-22 at 4.18.14 PM

This means we can set the background color

svg path#red {
fill: #F00;
}

and as long as we include the appropriate CSS transitions

svg path {
transition: fill 1.5s;
}

it will be animated.

As long as it’s not an image. (Sigh.)

Screen Shot 2014-10-22 at 4.18.58 PM

If you try to use fill in the traditional background image method (fill: url(some_image.jpg);), you’ll find it just fades to black. In order to use images as fills in SVG, we need to declare them as SVG patterns within the SVG object, then call it as a url reference in the CSS.

Screen Shot 2014-10-22 at 4.21.46 PM

<svg>
<pattern id='gop1' width="1" height="1" viewBox="0 0 100 100" preserveAspectRatio="none">
<image xlink:href='http://projects.ydr.com/election/img/back_gop1.jpg' width="100" height="100" preserveAspectRatio="none"></image>
</pattern>
</svg>

/* CSS */
fill: url(#red-stripes);

Of course, then we get into the transitions again. While the color fills animate properly (because they’re CSS elements), the patterns are SVG elements – and when CSS tries to transition between them, it just flips a switch (on or off). This, of course, fails my (increasingly less-simple-looking) three requirements.

Fortunately, there’s a way. SVG also has a fill-opacity property, which applies both straight color fills and pattern fills (because you’re affecting the whole path/object, whereas the pattern is specifically an SVG element). Using some nonsense with starting out with everything at black dialed down to 0.1 fill-opacity, we can add the pattern fill (very transparent), then bring up the fill-opacity to 1 — and add the fill-opacity to our transition list.

As you can imagine, this was a bit of a pain in the ass to figure out. When I started writing the code, I wanted to make sure it would be extensible and reusable in future map projects. Thus, the SVGLite jQuery plugin, now up at GitHub. It’s limited, but damn if it doesn’t make basic SVG manipulations a lot easier. Here’s an example of it in action.

Resources

SVGLite – Requires the jQuery plugin
RaphaelJS – A much larger, more robust vector image library
SVG patterns – Image fills
CSS transitions