A simpler interactive HTML map

The world, beautifully rendered by my hasty hand

Making a map interactive on a webpage has always been a bit of a pain. The basic problem is that elements on a webpage are rectangular, whereas countries and regions of the world are, well, lots of different random intersecting shapes. Clicking on a part of the map and turning that into a meaningful location isn't so hard, but highlighting regions when you hover over them is more difficult.

There's various ways around this problem, from the simple (using an imagemap) to the complex (licensing some complex SVG plugin). But how about good ol' fashioned straightforward HTML, CSS and JavaScript?

To be honest, I'm surprised by how often clients want interactive maps on their websites. Mainly because I have something of a pragmatic approach to any interface where I need to identify where I am. How often outside of a web browser does the average person interact with a real map? Give me a text box to type in my postcode and I'm happy. In short, I have a dislike of overly complex solutions to problems, which led me to the following experiment.

What if you overlaid a map with some absolutely positioned invisible elements that responded to an event by adding a background that made it look like part of the map had lit up when hovered? It's a good start, but they clash pretty quickly, as shown below.

Most of the regions overlap with another to some degree. Only one region can be on top and therefore subject to clicks/hover events.

Next, let's add in some sub elements to each region. These are highlighted in white. Even with just a few of them, it's possible to position them such that they cover most of the land mass they relate to and don't collide with the sub elements of another region. If we wanted we could add in loads of these to make the coverage much more accurate, but this is meant to be a simple demonstration.

Sub elements added to the main regions in white (only the elements for Africa and Asia are shown for simplicity).

Even if we do this and trigger the background change on the main region element by hovering over a sub element we still have an overlap problem. Even though the region elements aren't part of the interaction they still overlap each other, and therefore each other's sub elements. So let's get rid of them.

With main regions removed

We can do this by dropping the width and height of the region elements to zero. Because they are absolutely positioned and their sub elements are absolutely positioned in relation to them, it doesn't matter how big they are, so we can do away with them altogether until they need to be visible. A quick bit of JavaScript...

$('.subregion').hover(
    function(){
        $('.region').removeClass('active');
        $(this).closest('.region').addClass('active');
    },
    function(){
        $('.region').removeClass('active');
    }
);	

The active class on each region not only puts in the highlighted background image but restores the correct width and height. Since this event is only triggered when the mouse is over the sub elements the overlapping region problem is removed entirely. Here's a working example:

Click to test

And yes, it's hugely rough around the edges - I just put this together to demonstrate the principle. If you wanted to you could spend all day making dozens of intricately positioned sub elements to make the whole thing utterly perfect.

Anything else?

Lastly, nothing is any use on the Internet any more unless it's responsive. Fortunately, we can absolutely position elements using percentage values instead of pixels, so our regions can grow and shrink with a main map parent element. Unfortunately, if we switch the sub elements to using percentages, they suddenly disappear as well, because their dimensions and positions are suddenly based on a value of zero, whereas previously they were hard coded. The solution? A bit of Less CSS to the rescue.

@scaleFactor: 100;

@wAfrica: 19.2%;
@hAfrica: 45.3%;

@wAfrica1: 70%;
@hAfrica1: 45%;

.africa {
    left: 42.8%;
    top: 35.4%;
    width: @wAfrica / @scaleFactor;
    height: @hAfrica / @scaleFactor;

    .nub {
        &.one {
            width: @wAfrica1 * @scaleFactor;
            height: @hAfrica1 * @scaleFactor;
        }
    }


    &.active {
        width: @wAfrica;
        height: @hAfrica;
        background-image:url('../img/segment_africa.png');
        .nub {
            &.one {
                width: @wAfrica1;
                height: @hAfrica1;
            }
        }
    }
}

Using the maths functions in Less, we can scale and reposition the sub elements accordingly depending on whether the parent region element is hidden or shown. You could also use media queries to target specific device sizes with smaller images, if you wanted to do the whole thing properly.

That's my map technique. Simple, responsive, customisable and free. You can download the Less/CSS file here and use the JavaScript above (or just look at my github repo), and obviously you'll need to reposition all the regions and graphics to suit your own map.

Browser compatibility

As ever, we're plagued by the folly that is Internet Explorer, which doesn't like hover interactions on elements that effectively have no visible content. It can be fixed for IE9 by adding an effectively invisible background colour to the sub elements.

background-color:rgba(0,0,0,0.00001);

IE8 and 7 continue to resist the calm and reasonable voice of progress by not supporting rgba, so an even more bizarre workaround is needed - adding a non-existent background image. Astonishingly, this works.

Related

This article is tagged with