Improving image maps with SVG and XSLT

By Frederik Elwert

Introduction

(X)HTML has a feature for adding linked areas to images: Image maps. These serve their purpose well, but in some situations a graphically more appealing solution is desirable. In this article, I will take you step by step through an example of how XHTML, SVG and XSLT can be combined to create enhanced image maps.

Note: you can download the full code for this article and experiment with it yourself.

Step by step

SVG is a very powerful technology, but currently it has one issue with its popularity on the Web—its lack of support by Internet Explorer. You cannot afford to lock out such a large user base from your application, therefore this solution takes a progressive enhancement approach to ensure maximum compatibility. The basis of the application will be an ordinary image map that every browser can render, and the SVG enhancement will be overlaid on top of it. XSLT will transform the image map into SVG for capable browsers.

The image map code looks like so:

<img src="UK.png" width="271"
  height="599" alt="Map of the United Kingdom"
  usemap="#uk-map" />

<map name="uk-map" id="uk-map">
  <area shape="poly" coords="131,309,166,286,160,277,177,264,152,250,179,189,173,169,124,170,145,140,162,105,187,83,214,6,192,9,161,47,161,79,122,100,123,122,45,131,22,161,11,176,17,202,11,226,38,217,32,241,49,247,45,276,66,293,83,296,80,311,89,327,108,322" href="http://en.wikipedia.org/wiki/Scotland" title="Scotland" alt="Map of Scotland" />
  <area shape="poly" coords="140,383,145,396,138,402,141,442,152,456,136,470,118,463,100,456,87,460,74,443,108,424,108,409,92,407,100,391,92,379,104,369,117,377,131,373" href="http://en.wikipedia.org/wiki/Wales" title="Wales" alt="Map of Wales" />
  <area shape="poly" coords="131,309,166,286,160,277,177,264,196,316,226,345,236,391,270,398,270,430,248,460,263,462,262,479,229,498,208,495,189,503,175,499,155,506,142,498,125,518,103,513,85,529,64,522,93,492,99,478,110,469,136,470,152,456,141,442,138,402,145,396,140,383,131,373,137,364,132,346,121,327" href="http://en.wikipedia.org/wiki/England" title="England" alt="Map of England" />
  <area shape="poly" coords="20,312,23,301,51,287,63,289,84,329,64,353,45,351,38,340,28,350,10,344,1,330,10,314" href="http://en.wikipedia.org/wiki/Northern_Ireland" title="Northern Ireland" alt="Map of Northern Ireland" />
</map>

This is a map of the United Kingdom with the each constituent country linked to its respective Wikipedia article (view the example). The only visible feedback indicating that the image is clickable and what the areas represent is the changing mouse cursor and a tooltip. This is not very usable, and we can certainly do better.

Adding graphics to the map

Now we will enhance the image map by adding an SVG layer on top of it. SVG shapes create clickable areas that can carry hyperlinks, just as with ordinary image maps. But SVG allows us to add some useful effects that aren’t possible with those. Here, I’ll use this for two things: Reveal each clickable area when hovered over, and display their names clearly above the map.

First, we create an svg element right before the img element. By setting position:absolute on it, the SVG is taken out of the normal flow. Thus, it takes up no space, and the image will be displayed underneath the SVG when we specify its appearance and position.

<svg xmlns="http://www.w3.org/2000/svg" 
  xmlns:xlink="http://www.w3.org/1999/xlink" 
  style="position:absolute;" width="271" 
  height="599" pointer-events="visible">
  
  <a xlink:href="http://en.wikipedia.org/wiki/Northern_Ireland" 
  xlink:title="Northern Ireland">
    <polygon id="area_ni" class="area" points="20,312,23,301,51,287,63,289,84,329,64,353,45,351,38,340,28,350,10,344,1,330,10,314">
      <title>Northern Ireland</title>
      <desc>Map of Northern Ireland</desc>
    </polygon>
  </a>
  …
</svg>

To simulate the image map behaviour, a style of fill:none is applied to each clickable area. The pointer-events="visible" property ensures that the areas remain clickable even with no fill.

Now we have full control over the appearance and can choose to make the linked areas visible to the user. But that would mean quite a lot of distraction from the image, even if the areas are a bit translucent. So we will make the areas visible on when hovered over, providing some visual feedback while keeping out of the way when they are not needed.

This can easily be achieved with declarative animation. We set the polygon’s opacity to 0 and let it fade in on mouseover and fade out on mouseout:

<polygon id="area_ni" class="area" 
  points="20,312,23,301,51,287,63,289,84,329,64,353,45,351,38,340,28,350,10,344,1,330,10,314" opacity="0">
  <title>Northern Ireland</title>
  <desc>Map of Northern Ireland</desc>
  <animate attributeName="opacity" from="0" to="1" begin="mouseover" dur="0.5s" fill="freeze"/>
  <animate attributeName="opacity" to="0" begin="mouseout" dur="0.5s" fill="freeze"/>
</polygon>

This already gives us quite a nice effect that helps the user understand which regions of the image are linked. But it would be nice to give some more information as to what the countries are called. In classical image maps, this is normally done via the title attribute (which most browsers render as a small tooltip). We do have the xlink:title attribute here which describes the link. However, it might be desirable to have full control about the way text is rendered. Therefore, we explicitly create a text element that shows the title text when the pointer is above the shape:

<g class="text" opacity="0" 
  pointer-events="none" id="area_ni_text">
  <rect width="100%" height="35"/>
  <text x="135.5" y="25" text-anchor="middle">Northern Ireland</text>
  <animate attributeName="opacity" to="1" begin="area_ni.mouseover" dur="0.5s" fill="freeze"/>
  <animate attributeName="opacity" to="0" begin="area_ni.mouseout" dur="0.5s" fill="freeze"/>
</g>

rect creates a rectangle that is used to give the text a (semi-transparent) background. The actual styling is done by the CSS. The pointer-events="none" property ensures that the text doesn’t affect the mouseover behaviour, meaning that if the text overlaps an area shape, the text will be ignored when determining if the mouse is above the shape or not.

Now we’re almost there. We do already have the desired behaviour when using an SVG compliant browser, but declarative animation still isn’t supported by many browsers, eg Firefox. In the case of Firefox the shape and text will simply stay invisible (although the link still works). But we can easily work around this by adding a little bit of scripting. We therefore add the following to the beginning of the svg:

<defs>
  <script type="text/ecmascript">
  function set_opacity(evt, value){
    evt.target.setAttributeNS(null, "opacity", value);
    var id = evt.target.getAttributeNS(null, "id");
    text_elem = document.getElementById(id+"_text");
    text_elem.setAttributeNS(null, "opacity", value);
  }
  </script>
</defs>

Now we add onmouseover and onmouseout attributes to the shapes to set the opacity by script if the user agent doesn’t support SVG animation. This makes the area and title text visible on mouseover, but doesn’t provide the nice fade effect.

<polygon id="area_ni" class="area"
  points="41,290,60,289,84,329,62,358,9,346,1,328,19,299"
  opacity="0" onmouseover="set_opacity(evt, 1);"
  onmouseout="set_opacity(evt, 0);">
  …
</polygon>

Letting others do the work: Using XSLT to create the SVG map

While the result is visually appealing, it poses two problems: First, the SVG code is much more verbose that the image map we started with, so it takes longer to write and is more difficult to maintain. Second, the browser has to support XHTML as XML, since we are mixing two XML namespaces in the same document (XHTML and SVG); this isn’t supported by Internet Explorer.

Fortunately, we can kill these two birds with a single stone: XSLT. We only need an XHTML document with the image map; we can then use a linked XSLT stylesheet to transform this into the corresponding SVG code. Since Microsoft’s browsers support neither SVG nor XHTML served as XML, the stylesheet will return the document unchanged for these. Since they expect XSLT stylesheets to return an HTML representation of the served XML document, the XML mimetype is no issue once we apply an XSLT transformation.

The XSLT can be applied to the XHTML document using a xml-stylesheet processing instruction at the beginning of the file:

<?xml version="1.0" encoding="UTF-8"?>
  <?xml-stylesheet href="addsvg.xsl" type="text/xsl"?>
  <html xmlns="http://www.w3.org/1999/xhtml">
  …
</html>

Compatibility and Accessibility

In an ideal world, all web browsers could cope with the XML techniques used in these examples. But in reality, we have to deal with Internet Explorer’s inability to deal with XHTML as XML. So to ensure this works in IE, please take a note of the following points:

  • The mime type has to be application/xml, as IE doesn’t accept application/xhtml+xml. It’s probably a good idea to use an .xml file extension, since .xhtml is normally associated with application/xhtml+xml.
  • One must not give a doctype declaration. IE is locked out from accessing the XHTML DTD, since it unnecessarily tries to download it every time it reads an XHTML document. But the XHTML namespace provides enough information for browsers to render it properly, so the page works well without a doctype declaration.

On the other hand, using an XML mime type might result in accessibility issues, since not all client programs can handle XML or support XSLT. Especially text browsers like w3m refuse to display XML (and thus XHTML with an application/xml mime type). The original XHTML file ensures accessibility using title and alt attributes on the areas. The resulting SVG file retains accessibility using the xlink:title attribute and title and desc elements. Losing these features just because of the mime type is a bit of a miserable situation.

If the server supports it, choosing the mime type by client abilities might be the best solution. For example, using Apache’s mod_rewrite you can serve an .html file as text/html or application/xhtml+xml depending on what the client accepts. Just put the following into your .htaccess file:

RewriteEngine On
RewriteCond %{HTTP_ACCEPT} application/xhtml\+xml
RewriteCond %{HTTP_ACCEPT} !application/xhtml\+xml\s*;\s*q=0
RewriteCond %{REQUEST_URI} \.html$
RewriteCond %{THE_REQUEST} HTTP/1\.1
RewriteRule .* - "[T=application/xhtml+xml]"

Summary

We now have a cross-browser solution providing progressive enhancement depending on the capabilities of the browser. Non-SVG-capable browsers (eg, IE) will show the plain image map, while others (eg Opera, Firefox, Safari*) will use SVG to display the areas and title text. If declarative animation is supported (eg Opera, Safari 4*), a nice fade effect is provided too.

*Note: Safari on Windows has some serious display issues with the SVG overlay.

You can now check out the full example and download the XSLT stylesheet.

This article is licensed under a Creative Commons Attribution, Non Commercial - Share Alike 2.5 license.

Comments

The forum archive of this article is still available on My Opera.

No new comments accepted.