Scaling image maps

Scaling an image map's area coordinates when the target image is scaled

Recently, I was working on a project for a client that involved an image map. Without going into specifics, I’d set it up so that a tooltip would show up when a hotspot on the map was hovered over.

Trouble cropped up when this was combined with a responsive layout. The width of the content area was flexible, and images would scale down if the window size was reduced (using img { max-width: 100% } in the site’s CSS and $('img').removeAttr('height') in the javascript to get rid of each image’s height attribute so they scale correctly). Unfortunately this meant that the hotspots in the image map, defined as pixel coordinates, got out of whack when the image was scaled down. While area tags can be given percentage values for their coordinates, apparently this is not yet supported by most browsers.

So instead, I knocked up a bit of jQuery which will fix up each area’s coords attribute if necessary on document ready, and when the window is resized. This should work for any number of image maps on a page.

(function($) {

    // prevent errors in IE < 8
    if (!$('<area coords=1>').attr('coords')) return;

    // find all images attached to a map
    var imgs = $('img[usemap]');

    // store initial coords of each <area>
    $('area').each(function(i, v) {
        var area = $(v);
        area.data('coords', area.attr('coords'));
    });

    // on window resize, iterate through each image
    // and scale its map areas
    $(window).bind('resize.scaleMaps', function() {
        imgs.each(function(i, v) {
            scaleMap($(v));
        });
    }).trigger('resize.scaleMaps');

    // find image scale by comparing offset width with width attribute
    // if the scale has changed or not set,
    // scale its map's areas by this factor
    // and store the new scale using $.data()
    function scaleMap(img) {

        var mapName  = img.attr('usemap').replace('#', ''),
            map      = $('map[name="' + mapName + '"]'),
            imgScale = img.width() / img.attr('width');

        if (imgScale !== img.data('scale')) {
            map.find('area').each(function(i, v) {
                var area      = $(v),
                    coords    = area.data('coords').split(','),
                    newCoords = [];
                $.each(coords, function(j, w) {
                    newCoords.push(w * imgScale);
                });
                area.attr('coords', newCoords.join(','));
            });
            img.data('scale', imgScale);
        }
    }

}(jQuery));

A few things to be aware of:

  • You’ll see that this requires that a width attribute be set for the image, or the scaling factor will end up as NaN. Also it’ll only deal with images and maps that are present on page load – dynamically created or modified maps will require a bit more fiddling.
  • This will not work in versions of Internet Explorer prior to version 9, for a couple of reasons:
    • they don’t scale images proportionally – that is, set img {max-height:100%} and the image will be squished when the window size is reduced.
    • Bizarrely, IE < 8 doesn’t seem to be able to access the coords attribute, short of using a regex on the <area>s outerHTML.

    For me this isn’t an issue – as old IE doesn’t support media queries and therefore responsive layouts, I just serve them up a fixed-width layout so all image sizes should stay constant.

  • Clearly the areas will get smaller and smaller on narrower screen widths, which could potentially cause usability issues, but that’s another story.