Sunday, July 15, 2007

Javascript image anchor highlighting

When using an image as an anchor, you very often want to make the image "highlight" when you hover over it. We've all seen this before - create a highlighted version of the image, add some javascript, and you get the effect. Here is an example:



Here is the markup:


<img src="http://brewer.designvigilante.com/files/imageHover/moveDown.png"
onmouseover="this.setAttribute('src','.../moveDown_over.png');"
onmouseout="this.setAttribute('src','.../moveDown.png');"
/>


This works for one image, but quickly become bothersome as you use more images. So, how to pull this out into a script that does it automatically for us? First thought - walk the DOM and get all the images, then add the event handlers to each one. This will work, but it is not optimal:

  1. Walking the DOM can be expensive.
  2. If you have a lot of images, you're adding a lot of event handlers. Each one takes up memory, and is also a potential memory leak if you delete the element later on without deleting the handlers.
  3. If you add new images after the page loads, you'll have to remember to add the handlers to each.


We can avoid all of these problems by using . Event delegation allows us to attach one event handler to the document body. When an event is fired on a node, the event will bubble up DOM tree unless is is canceled. The event object contains the original target element, so we are able to write one function to handle image highlighting for the entire page. Here is what the event handler code should look like:

var handleMouseOver = function(e)
{
e = e || window.event; // handle IE
var el = e.target || e.srcElement; // e.target points to the element that was moused over. srcElement is for IE
// If the element is an image, and it's parent is an anchor
if( el.tagName.toLowerCase() == "img" && el.parentNode.tagName.toLowerCase() == "a" )
{
// add _over to the name of the image.
var src = el.getAttribute( "src" );
var dot = src.indexOf('.');
el.setAttribute( "src", src.substring( 0, dot ) + "_over" + src.substring( dot ) );
}
};

// Mouse out is the same, except removing '_over' from the image
var handleMouseOut = function( e ){
e = e || window.event;
var el = e.target || e.srcElement;
if( el.tagName.toLowerCase() == "img" && el.parentNode.tagName.toLowerCase() == "a" )
{
var src = el.getAttribute( "src" );
el.setAttribute( "src", src.replace( "_over", "" ) );
}
};


Note that you need to upload a highlighted version of the image for this to work.

So, how to bind these to the document body? We need o be concerned with handler collision - that is multiple handlers binding to the same event and overwriting each other. We will explore this more in a upcoming post, for now we'll just ignore this case.


var ImageAnchorHoverHighlighter=function(){
var handleMouseOver = function(e)
{
e = e || window.event;
var el = e.target || e.srcElement;
if( el.tagName.toLowerCase() == "img" && el.parentNode.tagName.toLowerCase() == "a" )
{
var src = el.getAttribute( "src" );
var dot = src.indexOf('.');
el.setAttribute( "src", src.substring( 0, dot ) + "_over" + src.substring( dot ) );
}
};

var handleMouseOut = function( e ){
e = e || window.event;
var el = e.target || e.srcElement;
if( el.tagName.toLowerCase() == "img" && el.parentNode.tagName.toLowerCase() == "a" )
{
var src = el.getAttribute( "src" );
el.setAttribute( "src", src.replace( "_over", "" ) );
}
};

return {
init : function(){
var b = document.body;
document.onmouseover = handleMouseOver;
document.onmouseout = handleMouseOut;
}
};
}();




Later on, I'll look at solving event collision, handling missing highlight image, labels, and background images.

1 comments:

said...

Event delegation is great, but have you considered using sprited CSS background images instead of an IMG tag (and changing the src attribute)? If you tile the two images (hover and normal) on the same image and shift its background-position by adding a new class, say, "hover", you get the added benefit of having the hover image preloaded, thus you avoid having to preload any of the hoverstate images. And, it's arguably semantically more appropriate to keep icons in CSS.