Mixing and matching SVG and Text

One of the happier side-effects of the recent Apple vs. Adobe fracas is that it has led to the widespread rediscovery of Scalable Vector Graphics as a means of displaying illustrations and animations on the web. Long the poster-child of standards nerds, SVG was a spec I’d heard of before – but only in contexts that suggested its sole raison d’etre was to be yet another standard with which to beat IE6 over the head for not implementing. I’d looked into it, briefly, back in 2003 or so – but the syntax was forbidding (XML isn’t offhand the format you’d think best suited for describing graphics) and the documentation unfriendly to the point of absurdity. I didn’t think of looking at it again until I started thinking a bit about visualisations for humanities data, and needed a cross-platform iDevice-compatible animation medium.

And what a happy rediscovery that was: clearly, I owe the standards nerds an apology. There are now a couple of good Javascript libraries available for SVG (Raphael and a JQuery plug-in) that fix all the x-browser problems for you and help smooth away SVG’s awkward XML-ness – which is, furthermore, nowhere near as difficult as I’d concluded it was all those years ago. The spec is (reasonably) small, supple, and does what you want it to with a bare minimum of muss and fuss. All that and SVG elements exist within the DOM – meaning that, unlike children of the <canvas> element, you can access them using standard JS traversal methods, trigger events affecting the rest of the page with them, etc.

In fact, SVG would be pretty much perfect for creating highly interactive, data-intensive pages – were it not for the way it handles text. There is a text element in the SVG spec – but it doesn’t do what you want it to: i.e., style or wrap like HTML text does. And the workarounds are horrendous.

After a bit of Googling I discovered what seemed like a solution: the svg:foreignObject element can wrap non-SVG objects, and I was assured that in this way I could simply drop HTML content into the middle of my SVG code and all would be well.

Except, of course, that it wasn’t. In place of the lovingly-prepared HTML content I’d been expecting to appear in the middle of my screen, I got nothing. Nada. Nyet. Zip. Zilch. Nothing. And upon tracking the foreignObject examples back to their lair, I saw the reason why: while Raphael and JQuery assume that what you’re trying to do is drop SVG elements into the middle of a standard HTML page, foreignObject code only renders properly in all browsers tested if the page is served with MIME-type svg. Which of course then banjaxes every standard HTML element you’ve got on your page, i.e., most of it.

It seemed, for an embarrassingly long time, like this was a show-stopper, before the key to the solution occurred to me – that there’s absolutely no reason why the HTML elements ‘within’ an SVG graphic need to be children of the SVG element itself. Using Raphael (and presumably JQuery, though I haven’t tested this), your first step is to define the area that will hold your SVG graphic by specifying its coordinates (x and y) and dimensions (width and height). After that, you’re probably going to spend some time positioning graphical objects within this area, using the same metrics. And at that point you’ve got all the information you need simply to append your HTML elements after the SVG outer element and position them absolutely ‘inside’ it.

It’s so ridiculously easy I’ll repeat that again: don’t bother with the workarounds in SVG. Just position your HTML absolutely and on top of it.

This is, admittedly, a kludge – but it works, it’s easy – and it lets you spend all the time you would have wasted trying to get the svg:text and svg:tspan elements working across different data actually working on the visualisation.

Leave a Reply

Your email address will not be published. Required fields are marked *