Web DevCenter
oreilly.comSafari Books Online.Conferences.
MySQL Conference and Expo April 14-17, 2008, Santa Clara, CA

Sponsored Developer Resources

Web Columns
Adobe GoLive
Essential JavaScript
Megnut

Web Topics
All Articles
Browsers
ColdFusion
CSS
Database
Flash
Graphics
HTML/XHTML/DHTML
Scripting Languages
Tools
Weblogs

Atom 1.0 Feed RSS 1.0 Feed RSS 2.0 Feed

Learning Lab






JavaScript & DHTML Cookbook

Super-Efficient Image Rollovers

by Danny Goodman, author of JavaScript & DHTML Cookbook
07/01/2003

Editor's note: If you've been coming by the Web DevCenter over the last month and a half, you've probably read the recipes we've excerpted from Danny Goodman's book JavaScript & DHTML Cookbook. This week Danny is back with a bonus recipe on reducing image rollover downloads that you won't find in his book. Enjoy.

Introduction

Related Reading

JavaScript & DHTML Cookbook
Solutions and Example for Web Programmers
By Danny Goodman

Like most practical DHTML solutions, this bonus recipe arose from a real-world need. An application I was working on featured a menubar and a tool palette. Both of these highly graphical items had numerous clickable regions that needed to change image state whenever the user rolled the cursor or pressed down on the button graphic. With three image states per "hot spot" plus surrounding border art chunks, the total image count was more than 60. Each time the page loaded, it made more than 60 image file requests to the server, a totally unacceptable proposition.

The solution--an adaptation of a technique employed in early days of Java applets--reduced the combined image total to six: three each for the menubar and tool palette. The code, which works with all W3C DOM-compatible browsers, relies on the same HTML client-side image maps that HTML authors have used for years. As an added benefit, the technique lets the image maps work as they always did, permitting ready access to automated web crawlers and browsers designed for accessibility.

Bonus Recipe: Reducing Image Rollover Downloads

NN 6, IE 5

Problem

You want to reduce the number of individual image files downloaded to the browser to accomplish three-state image rollovers.

Solution

The solution is a combination of traditional HTML techniques plus a small JavaScript library shown in the Discussion section. The script library makes some minor assumptions about identifiers assigned to the id attributes of the <img> tags and name attribute of the <map> tag, but takes most of its cues from HTML attribute values.

Begin by creating one large image for the entire rollover area in each of the visual states of the buttons or controls. Include graphical borders or other static artifacts associated with the active region. The script described here accommodates three states (normal, mouseover, mousedown), which means you should create three images, each one displaying all of the buttons in the same state. Here are three images from the example:

Image for "normal" state:
normal menubar

Image for "mouseover" state:
mouseover menubar

Image for "mousedown" state:
mousedown menubar

The names you assign to the three image files are not critical. It may be helpful for development purposes to establish a system that clearly identifies each image's purpose and state. For example, the following sample file names readily identify the three states of a menubar image:

  • menubarUp.jpg
  • menubarOver.jpg
  • menubarDown.jpg

Place the images in your page's HTML as absolute-positioned elements (stacked atop each other) nested inside another relative- or absolute-positioned container (such as a div element). Assign unique identifiers to each img element's id attribute so that the three share the same prefix of your choice and end with suffixes Up, Over, and Down. Insert the <img> tags into the page with the normal (up) version occurring first in source code order. Assign style sheet rules that make the normal version visible by default, but the other two hidden by default, as shown in the following example:

<div style="position:relative">
   <img id="menubarUp" src="menubarUp.jpg" height="110" width="680" alt="menubar"
      border="0" style="position:absolute; top:0px; left:0px; 
      visibility:visible" usemap="#menubar" />
   <img id="menubarOver" src="menubarOver.jpg" height="110" width="680" alt="menubar"
      border="0" style="position:absolute; top:0px; left:0px; 
      visibility:hidden" usemap="#menubar" />
   <img id="menubarDown" src="menubarDown.jpg" height="110" width="680" alt="menubar"
      border="0" style="position:absolute; top:0px; left:0px; 
      visibility:hidden" usemap="#menubar" />
</div>

Define client-side image map elements (via <map> and <area> elements). Use the prefix of the id attribute values from the img elements as the value for the name attribute of the map element. Also assign a unique identifier of your choice to the id attribute of the <map> tag. Set the rectangular coordinates of the "hot spots" in the image as you normally would for an image map. For example:

<map id="menubarMap" name="menubar">
   <area shape="rect" coords="8,22,117,86" href="index.html" alt="new" title="new">
   <area shape="rect" coords="120,22,227,86" href="products.html" 
       alt="products" title="view products" />
   <area shape="rect" coords="230,22,337,86" href="manuals.html" 
       alt="manuals" title="download manuals" />
   <area shape="rect" coords="340,22,447,86" href="dealers.html" 
       alt="dealers" title="find a dealer" />
   <area shape="rect" coords="450,22,557,86" href="support.html" 
       alt="support" title="get support" />
   <area shape="rect" coords="560,22,667,86" href="contact.html" 
       alt="contact" title="contact us" />
</map>

Include the JavaScript code from the Discussion section in your page, either inside a <script> tag or linked in from and external .js file. Initialize the code from the page's onload event handler:

<body ... onload="initMaps('menubarMap')">

Pass as a parameter the string ID of the map element you wish to initialize. If you have multiple image maps, pass their IDs as comma-delimited arguments in the call to the initMaps() function.

Discussion

The JavaScript portion of this recipe follows:

function initMaps() {
   if (document.getElementById) {
      var mapIds = initMaps.arguments;    // pass string IDs of containing map elements
      var i, j, area, areas;
      for (i = 0; i < mapIds.length; i++) {
        areas = document.getElementById(mapIds[i]).getElementsByTagName("area");

        for (j = 0; j < areas.length; j++) {  // loop thru area elements
           area = areas[j];
           area.onmousedown = imgSwap;    // set event handlers
           area.onmouseout = imgSwap;
           area.onmouseover = imgSwap;
           area.onmouseup = imgSwap;
        }
      }
   }
}

// image swapping event handling
function imgSwap(evt) {
   evt = (evt) ? evt : event;                   // equalize event models
   var elem = (evt.target) ? evt.target : evt.srcElement;
   var imgClass = elem.parentNode.name;         // get map element name
   var coords = elem.coords.split(",");         // convert coords to clip
   var clipVal = "rect(" + coords[1] + "px " +
                           coords[2] + "px " +
                           coords[3] + "px " +
                           coords[0] + "px)";
   var imgStyle;
   
   switch (evt.type) {
      case "mousedown" :
         imgStyle = document.getElementById(imgClass + "Down").style;
         imgStyle.clip = clipVal;
         imgStyle.visibility = "visible";
         break;
      case "mouseout" :
         document.getElementById(imgClass + "Over").style.visibility = "hidden";
         document.getElementById(imgClass + "Down").style.visibility = "hidden";
         break;
      case "mouseover" :
         imgStyle = document.getElementById(imgClass + "Over").style;
         imgStyle.clip = clipVal;
         imgStyle.visibility = "visible";
         break
      case "mouseup" :
         document.getElementById(imgClass + "Down").style.visibility = "hidden";
         // guarantee click in IE
         if (elem.click) {
             elem.click();
         }
         break;
   }
   evt.cancelBubble = true;
   return false;
}

After the page loads, the initMaps() function assigns four mouse-related event handlers to each of the area elements within the map elements whose IDs arrive in the initialization function's arguments. All four mouse actions--mouseover, mouseout, mousedown, and mouseup--invoke the same event handler function. Event handlers are assigned only if the browser supports at least basic W3C DOM capabilities needed for the rest of the image handling.

The lone event handler function, imgSwap(), performs a lot of work but in a speedy and efficient manner. The function's primary jobs are: 1) to determine which of the three images needs to be visible or hidden (depending on event type); and 2) to adjust the clipping rectangle of the visible image to the dimensions of the specific area element being activated by the event.

The imgSwap() function begins by equalizing the Internet Explorer and W3C DOM event models to obtain a single reference to the event object (see Recipe 9.1) as well as a reference to the area element that received the event. The area element's parent node (the map element container) has a name that becomes an important part of the image visibility swapping later in the function. It's economical to grab that property just once and assign it to a local variable (imgClass) for use later.

Next, the function extracts the area element's coordinate values. These values are to be assigned to a clipping rectangle, but because the coords and style.clip property values are in different sequences and formats, the function needs to convert from one format to the other. For example, the first area element's coords value must transform from:

   8, 22, 117, 86

to the clip style property value of:

   rect(22px 117px 86px 8px)

With the essential variable values loaded for action, the imgSwap() function then branches execution based on the specific mouse event type. This is where the naming conventions for the id attributes of the img elements come into play. For example, during a mouseover event, the "menubarOver" image is clipped to the target element's coordinates, and then made visible; during a mouseout event, both of the secondary images are hidden from view.

You can see an example of the effect in action for a six-button graphical menubar. Note that the same two functions shown above can be used with any number of three-image sets of rollover graphics on the same page. Simply add the name of the surrounding map elements as arguments to the initMaps() function call. The scripts pick up the clipping values from the coords attribute values of your area elements. If you or the page's graphic designer change the menubar art or image map coordinates, the scripts automatically pick up the changes from the revised HTML.

Not only does this solution significantly reduce the number of http requests to the server for each group of rollover images, but it also eliminates the need to pre-cache images explicitly. All necessary images are included in the document's body, forcing all images to download with the page without any script intervention.

See Also

Recipe 9.1 of JavaScript & DHTML Cookbook about equalizing the incompatible Internet Explorer and W3C DOM event models; Recipe 4.8 for techniques to improve script performance.

Danny Goodman has been writing about technology and computers full-time since 1981 and is the author of Dynamic HTML: The Definitive Reference and "The Complete HyperCard Handbook."


O'Reilly & Associates recently released (April 2003) JavaScript & DHTML Cookbook.


Return to the Web Development DevCenter.