Introduction
The HTML map element defines a client-side image map: a set of regions that are bound to an img
element. From a web designer's point of view, it would be nice to be able to highlight or replace the currently active region. Pure HTML doesn't provide a way to alter the contents of an image map. area
tags -- the immediate children of the map
-- are nothing more than hyperlink hot spots. They do not allow any styling, such as changing a background image or border color, for instance. The easiest way to indicate a current map region via JavaScript is to change the entire image on the mouseover
event and then restore the original on mouseout
. Here is an example. This method can be very bandwidth-consuming if the target image is big. Another way, which involves CSS and overlay images, is much better in terms of bandwidth, but is very complex to install.
This article explains an alternative technique for changing the appearance of an image map's area
s that is easy to use and easy enough to set up. Also presented is a script, the Lightmapper, that uses this technique. Installation of the script requires a casual knowledge of HTML and JavaScript, as well as basic image editing skills. The script is perfectly compatible with all mainstream browsers: Mozilla/Firefox 2, Opera 9 and MS IE 6/7. Lightmapper script and the accompanying art are distributed under the zlib/libpng license.
Setup
The setup procedure assumes the following:
- You have the image -- in JPEG, GIF or PNG format -- to be bound to the image map. I'll use a scanned black and white map of Europe.
- You have already made the image map itself, so now you have HTML code with
map
and area
tags. I've used the Imagemapic, which is a free tool for image mapping. However, there are many tools on the Internet that do this. - You have a raster image editor advanced enough to handle transparency. Paint.NET or GIMP will be okay. The following explanation assumes that you use Paint.NET.
At first, you have to prepare the overlay images, i.e. the images that will be shown when the cursor enters the respective area
. Basically, these images are the areas you've previously defined in the image map. They have been cut from the original "big" image and pasted to the new smaller images. Do the following or skip it if you understand what I meant:
Open your "big" image in a image editor.
For each region:
Use the rectangular selection tool to encompass the region in a selection, using zooming to be more accurate.
Copy the selection to the clipboard either by selecting "Edit" from the menu -> "Copy" item or by pressing Ctrl+C.
Create a new image via "File" from the menu -> "New" item. With your selection in the clipboard, the image dimensions will be set to fit this selection.
Paste the clipboard contents into the new image by selecting "Edit" from the menu -> "Paste" item or by pressing Ctrl+P.
If the area/region in the image map is not rectangular, you should use the "Eraser" tool (E key) to erase the contents of the image that go out of the region the overlay is intended for. To do this:
Select the "Eraser" tool in the tools window or press the E key
In the "Colors" window, select "Secondary" from the drop-down list. Click the "More" button and then move the slider under "Transparency - Alpha" to 0.
Accurately erase any parts of the image that do not belong to the desired area.
Edit the new image. For instance, you can use the "Paint Bucket" (F key) to change the color of the whole image.
Note that if the selected area has exclaves, these should be painted on the overlay image with the parent area. However, these can and should be defined separately in the image map. Doing this increases control usability.
The artistic part is now over. Next comes the programming:
- Make sure your
map
tag has both id
and name
attributes and that these are equal.
<map id="map_of_europe" name="map_of_europe">
- Make sure that all
area
s within a map have an id
attribute.
<area href="#" id="Austria" shape="polygon"
coords="230, 319, 236, 325, 251, ... >
- Include the Lightmapper script file into your HTML page.
<html>
<head>
<script type="text/javascript" src="lightmapper.js"></script>
...
- In the page's
head
section, create a script block that will contain the setup function.
<html>
<head>
<script type="text/javascript" src="lightmapper.js"></script>
<script type="text/javascript"></script>
- Start Lightmapper. The code will look like this:
<html>
<head>
<script type="text/javascript" src="lightmapper.js"></script>
<script type="text/javascript">
function mouse_over(id)
{
document.getElementById("name").innerHTML = id;
}
function mouse_out(id)
{
document.getElementById("name").innerHTML = "";
}
function prepare()
{
new Lightmapper(
"map_of_europe", "image_of_europe",
mouse_over, mouse_out, [
["Albania", "images/_albania.gif", 1, -2],
["Austria", "images/_austria.gif", -1, -2],
["Belarus", "images/_belarus.gif", -2, 3],
["Belgium", "images/_belgium.gif", 1, 3],
...
["Russia", "images/russia.gif", 1, -7],
["Russia_Kaliningrad", "images/russia.gif",
-2, 1, , , "Russia"],
...
["Spain", "images/_spain.gif", -1, -5],
["Switzerland", "images/_switzerland.gif", -2, 4],
["Sweden", "images/_sweden.gif", -2, 5],
["Ukraine", "images/_ukraine.gif", -2, 0] ]);
}
</script>
- Bind the
prepare
function to the body
's onload
event.
<html>
...
<body onload="prepare()">
...
Constructor and inner workings
The constructor of the Lightmapper
object may seem quite complex. In fact, it is complex until you understand the purpose of each parameter.
function Lightmapper(imgId,
mouseOver,
mouseOut,
bindings)
Note that the script makes use of displacement values, specified in pixels, that are used to align the mouse-over image of an area with the original image. These values must be found experimentally by your artist/programmer during the art setup. Provided all of the above, the script does the following:
- It calculates the position of the target image, in pixels, relative to the
body
of the document.
var ct = document.getElementById(this.imgId);
var initX = 0, initY = 0, ct_ = ct;
if(ct_.offsetParent)
{
while(ct_.offsetParent)
{
initX += ct_.offsetLeft;
initY += ct_.offsetTop;
ct_ = ct_.offsetParent;
}
}
else if(ct_.x)
{
initX += ct_.x;
initY += ct_.y
}
- For each
area
listed in the array of bindings:
for(var i = 0, l = this.binds.length; i < l; i++)
- Given the
area
's coordinates, i.e. the coords
attribute, the position of the image and the displacement values, the script calculates the position of the overlay image associated with the current area
.
var xmin = 0, xmax = 0, ymin = 0, ymax = 0;
var coords = area.coords.split(",");
xmin = xmax = +coords[0];
ymin = ymax = +coords[1];
var m = coords.length, k = 2;
while(k < m)
{
var x = +coords[k++];
var y = +coords[k++];
if(x < xmin) xmin = x;
if(x > xmax) xmax = x;
if(y < ymin) ymin = y;
if(y > ymax) ymax = y;
}
- It creates an absolutely positioned
div
element and places it in the recently calculated position.
var div = elem.overlay = document.createElement("DIV");
div.id = "ml_div_" + String(Lightmapper.prototype._idCount++);
...
var dts = div.style;
dts.position = "absolute";
dts.top = ymin + initY + elem["ydsp"];
dts.left = xmin + initX + elem["xdsp"];
dts.width = xmax - xmin;
dts.height = ymax - ymin;
dts.opacity = 0;
dts.filter = "alpha(opacity=0)";
dts.display = "none";
- It creates an
img
element, sets its src
attribute to the path to an overlay image and attaches this img
to the recently created div
.
var img = document.createElement("IMG");
img.src = elem["isrc"];
img.border = "0";
...
div.appendChild(img);
document.body.appendChild(div);
- It creates a new
map
with a new single area
containing the region of the original area
, but transformed to the image-space of the overlay image. It then attaches this map
to the img
created earlier.
var map = document.createElement("map");
var mid = map.name = map.id = div.id + "_bound_map";
img.useMap = "#" + mid;
var area_new = document.createElement("area");
area_new.href = area.href;
area_new.shape = area.shape;
k = 0;
while(k < m)
{
coords[k++] -= xmin;
coords[k++] -= ymin;
}
area_new.coords = coords.join(",");
map.appendChild(area_new);
document.body.appendChild(map);
- It attaches a
mouseover
event to the original area
and a mouseout
event to the new area
.
var mi = elem["musi"] || this.mouseOver;
var mo = elem["muso"] || this.mouseOut;
this._setup_event(area, 'mouseover',
this._callLater(this._fade, div.id, 100, 50, 20, mi));
this._setup_event(elem["prnt"] ? area : area_new, 'mouseout',
this._callLater(this._fade, div.id, 0, 50, 20, mo));
To be continued?
This project is in its infancy; it will be developed and expanded. If you wish to help, please vote for this article and/or any other articles of mine. It is the best help I can dream of. :-) For other means of help, please see the donate.txt file in the source archive. Thank-you.
History
- May 23rd, 2007 - Initial release
- July 30th, 2007 - Bugfix release