home | O'Reilly's CD bookshelfs | FreeBSD | Linux | Cisco | Cisco Exam  


JavaScript: The Definitive GuideJavaScript: The Definitive GuideSearch this book

14.6. Images

The images[] property of the Document object is an array of Image elements, each representing one of the inline images, created with an <img> tag, that is contained in the document. The images[] array and the Image object were added in JavaScript 1.1. While web browsers have always been able to display images with the <img> tag, the addition of the Image object was a major step forward -- it allowed programs to dynamically manipulate those images.

14.6.1. Image Replacement with the src Property

The main feature of the Image object is that its src property is read/write. You can read this property to obtain the URL from which an image was loaded, and, more importantly, you can set the src property to make the browser load and display a new image in the same space. For this to work, the new image must have the same width and height as the original one.

In practice, the most common use for image replacement is to implement image rollovers, in which an image changes when the mouse pointer moves over it. When you make images clickable by placing them inside your hyperlinks, rollover effects are a powerful way to invite the user to click on the image. Here is a simple HTML fragment that displays an image within an <a> tag and uses JavaScript code in the onmouseover and onmouseout event handlers to create a rollover effect:

<a href="help.html"
   onmouseover="document.helpimage.src='images/help_rollover.gif';"
   onmouseout="document.helpimage.src='images/help.gif';">
<img name="helpimage" src="images/help.gif" width="80" height="20" border="0">
</a>

Note that in this code fragment we gave the <img> tag a name attribute, to make it easy to refer to the corresponding Image object in the event handlers of the <a> tag. We used the border attribute to prevent the browser from displaying a blue hyperlink border around the image. The event handlers of the <a> tag do all the work: they change the image that is displayed simply by setting the src property of the image to the URLs of the desired images.

The ability to dynamically replace one image in a static HTML document with another image opens the door to any number of special effects, from animation to digital clocks that update themselves in real time. With a bit of thought, you can probably imagine many more potential uses for this technique.

14.6.2. Offscreen Images and Caching

To make image-replacement techniques viable, the animations or other special effects need to be responsive. This means that we need some way to ensure that the necessary images are "pre-fetched" into the browser's cache. To force an image to be cached, we first create an offscreen image using the Image( ) constructor. Next, we load an image into it by setting its src property to the desired URL (exactly as we would do for an onscreen image). Later, when the same URL is used for an onscreen image, we know it can be quickly loaded from the browser's cache, rather than slowly loaded over the network. Note that we never actually do anything with the offscreen image we create. In particular, we do not assign the offscreen Image object into the images[] array of the document.

The image-rollover code fragment shown in the previous section did not pre-fetch the rollover image it used, so the user will probably notice a delay in the rollover effect the first time she moves the mouse over the image. To fix this problem, we could modify the code as follows.

<script>
// Create an offscreen image and pre-fetch the rollover image.
// Note that we don't bother saving a reference to the offscreen image,
// since there is nothing we can do with it later.
(new Image(80,20)).src = "images/help_rollover.gif";
</script>
<a href="help.html"
     onmouseover="document.helpimage.src='images/help_rollover.gif';"
     onmouseout="document.helpimage.src='images/help.gif';">
<img name="helpimage" src="images/help.gif" width="80" height="20" border="0">
</a>

Example 14-3 shows code that performs a simple animation using image replacement and uses offscreen images to pre-fetch the frames of the animation. Note that in this example we retain the offscreen image objects we create, because they are a convenient way to hold the URLs of the images that make up the animation. To perform the animation, we assign the src property of one of the offscreen images to the src property of the onscreen image that is the subject of the animation.

Example 14-3. An animation using image replacement

<!-- The image that will be animated. Give it a name for convenience. -->
<img name="animation" src="images/0.gif">

<script>
// Create a bunch of offscreen images, and pre-fetch the "frames"
// of the animation into them so that they're cached when we need them
var aniframes = new Array(10);
for(var i = 0; i < 10; i++) {
    aniframes[i] = new Image( );                 // Create an offscreen image
    aniframes[i].src = "images/" + i + ".gif";    // Tell it what URL to load
}

var frame = 0;          // The frame counter: keeps track of current frame
var timeout_id = null;  // Allows us to stop the animation with clearTimeout( )

// This function performs the animation. Call it once to start.
// Note that we refer to the onscreen image using its name attribute.
function animate( ) {
    document.animation.src = aniframes[frame].src;    // Display the current frame
    frame = (frame + 1)%10;                           // Update the frame counter
    timeout_id = setTimeout("animate( )", 250);      // Display the next frame later
}
</script>

<form>  <!-- This form contains buttons to control the animation -->
  <input type="button" value="Start"
         onclick="if (timeout_id == null) animate( );">
  <input type="button" value="Stop"
         onclick="if (timeout_id) clearTimeout(timeout_id); timeout_id=null;">
</form>

14.6.3. Image Event Handlers

In Example 14-3, our animation does not begin until the user clicks the Start button, which allows plenty of time for our images to be loaded into the cache. But what about the more common case in which we want to automatically begin an animation as soon as all the necessary images are loaded? It turns out that images, whether created onscreen with an <img> tag or offscreen with the Image( ) constructor, have an onload event handler that is invoked when the image is fully loaded.

The following code fragment shows how we could modify Example 14-3 to use this event handler to count the number of images that have loaded and automatically start the animation when all the images have loaded. Since offscreen images are not part of the HTML document, the event handler cannot be assigned as an HTML attribute. Instead, we simply assign a function to the onload property of each Image object we create. When each image is loaded, the browser calls the function.

var aniframes = new Array(10);  // Hold the offscreen animation frames.
var num_loaded_images = 0;      // How many have been loaded so far?

// This function is used as an event handler. It counts how many images
// have been loaded and, when all have been loaded, it starts the animation.
function countImages( ) {
    if (++num_loaded_images == aniframes.length) animate( );
}

// Create the offscreen images and assign the image URLs.
// Also assign an event handler to each image so we can track how many images
// have been loaded. Note that we assign the handler before the URL, because
// otherwise the image might finish loading (e.g., if it is already cached)
// before we assign the handler, and then the handler would never be triggered.
for(var i = 0; i < 10; i++) {
    aniframes[i] = new Image( );                 // Create an offscreen image
    aniframes[i].onload = countImages;            // Assign the event handler
    aniframes[i].src = "images/" + i + ".gif";    // Tell it what URL to load
}

In addition to the onload event handler, the Image object supports two others. The onerror event handler is invoked when an error occurs during image loading, such as when the specified URL refers to corrupt image data. The onabort handler is invoked if the user cancels the image load (for example, by clicking the Stop button in the browser) before it has finished. For any image, one (and only one) of these handlers is called.

In addition to these handlers, each Image object also has a complete property. This property is false while the image is loading; it is changed to true once the image has loaded or once the browser has stopped trying to load it. In other words, the complete property becomes true after one of the three possible event handlers is invoked.

14.6.5. Image-Replacement Example

Because image replacement is such a versatile technique, we'll end our discussion of the Image object with an extended example. Example 14-4 defines a ToggleButton class that uses image replacement to simulate a graphical checkbox. Because this class uses images that we provide, we can use bolder graphics than those plain old graphics used by the standard HTML Checkbox object. Figure 14-3 shows how these toggle-button graphics could appear on a web page. This is a complex, real-world example and is worth studying carefully.

Figure 14-3

Figure 14-3. ToggleButtons implemented with image replacement

Example 14-4. Implementing a ToggleButton with image replacement

<script language="JavaScript1.1">
// This is the constructor function for our new ToggleButton class.
// Calling it creates a ToggleButton object and outputs the required
// <a> and <img> tags into the specified document at the current location. 
// Therefore, don't call it for the current document from an event handler.
// Arguments:
//    document: The Document object in which the buttons are to be created.
//    checked:  A boolean that says whether the button is initially checked.
//    label:    An optional string that specifies text to appear after the button.
//    onclick:  An optional function to be called when the toggle button is
//              clicked. It is passed a boolean indicating the new state of
//              the button. You can also pass a string, which is converted
//              to a function that is passed a boolean argument named "state".
function ToggleButton(document, checked, label, onclick)
{
    // The first time we are called (and only the first time), we have
    // to do some special stuff. First, now that the prototype object
    // is created, we can set up our methods. 
    // Second, we need to load the images we'll be using.
    // Doing this gets the images in the cache for when we need them.
    if (!ToggleButton.prototype.over) {
        // Initialize the prototype object to create our methods
        ToggleButton.prototype.over = _ToggleButton_over;
        ToggleButton.prototype.out = _ToggleButton_out;
        ToggleButton.prototype.click = _ToggleButton_click;

        // Now create an array of Image objects and assign URLs to them.
        // The URLs of the images are configurable and are stored in an
        // array property of the constructor function itself. They are
        // initialized below. Because of a bug in Netscape, we have
        // to maintain references to these images, so we store the array
        // in a property of the constructor rather than using a local variable.
        ToggleButton.images = new Array(4);
        for(var i = 0; i < 4; i++) {
            ToggleButton.images[i] = new Image(ToggleButton.width,
                                               ToggleButton.height);
            ToggleButton.images[i].src = ToggleButton.imagenames[i];
        }
    }
    
    // Save some of the arguments we were passed
    this.document = document;
    this.checked = checked;

    // Remember that the mouse is not currently on top of us
    this.highlighted = false;

    // Save the onclick argument to be called when the button is clicked.
    // If it is not already a function, attempt to convert it
    // to a function that is passed a single argument, named "state".
    this.onclick = onclick;
    if (typeof this.onclick == "string")
        this.onclick = new Function("state", this.onclick);

    // Figure out what entry in the document.images[] array the images
    // for this checkbox will be stored in
    var index = document.images.length;

    // Now output the HTML code for this checkbox. Use <a> and <img> tags.
    // The event handlers we output here are confusing but crucial to the
    // operation of this class. The "_tb" property is defined below, as
    // are the over(), out(), and click( ) methods.
    document.write('&nbsp;<a href="about:blank" ' +
      'onmouseover="document.images[' + index + ']._tb.over( );return true;" '+
      'onmouseout="document.images[' + index + ']._tb.out( )" '+
      'onclick="document.images[' + index + ']._tb.click( ); return false;">');
    document.write('<img src="' + ToggleButton.imagenames[this.checked+0] +'"'+
                   ' width=' + ToggleButton.width +
                   ' height=' + ToggleButton.height +
                   ' border="0" hspace="0" vspace="0" align="absmiddle">');
    if (label) document.write(label);
    document.write('</a></br>');

    // Now that we've output the <img> tag, save a reference to the
    // Image object that it created in the ToggleButton object
    this.image = document.images[index];

    // Also make a link in the other direction, from the Image object
    // to this ToggleButton object. Do this by defining a "_tb" property
    // in the Image object.
    this.image._tb = this;
}

// This becomes the over( ) method
function _ToggleButton_over( )
{
    // Change the image, and remember that we're highlighted
    this.image.src = ToggleButton.imagenames[this.checked + 2];
    this.highlighted = true;
}

// This becomes the out( ) method
function _ToggleButton_out( )
{
    // Change the image, and remember that we're not highlighted
    this.image.src = ToggleButton.imagenames[this.checked + 0];
    this.highlighted = false;
}

// This becomes the click( ) method
function _ToggleButton_click( )
{
    // Toggle the state of the button, change the image, and call the
    // onclick method, if it was specified for this ToggleButton
    this.checked = !this.checked;
    this.image.src = ToggleButton.imagenames[this.checked+this.highlighted*2];
    if (this.onclick) this.onclick(this.checked);
}

// Initialize static class properties that describe the checkbox images. These
// are just defaults. Programs can override them by assigning new values.
// But they should be overridden *before* any ToggleButtons are created.
ToggleButton.imagenames = new Array(4);             // Create an array
ToggleButton.imagenames[0] = "images/button0.gif";  // The unchecked box
ToggleButton.imagenames[1] = "images/button1.gif";  // The box with a checkmark
ToggleButton.imagenames[2] = "images/button2.gif";  // Unchecked but highlighted
ToggleButton.imagenames[3] = "images/button3.gif";  // Checked and highlighted
ToggleButton.width = ToggleButton.height = 25;      // Size of all images
</script>

<!-- Here's how we might use the ToggleButton class -->
Optional extras:<br>
<script language="JavaScript1.1">
// Create ToggleButton objects and output the HTML that implements them
// One button has no click handler, one has a function, and one has a string
var tb1 = new ToggleButton(document, true, "56K Modem");
var tb2 = new ToggleButton(document, false, "Laser Printer",
                           function(clicked) {alert("printer: " + clicked);});
var tb3 = new ToggleButton(document, false, "Tape Backup Unit",
	                       "alert('Tape backup: ' + state)");
</script>

<!-- Here's how we can use the ToggleButton objects from event handlers -->
<form>
<input type="button" value="Report Button States"
       onclick="alert(tb1.checked + '\n' + tb2.checked + '\n' + tb3.checked)">
<input type="button" value="Reset Buttons"
       onclick="if (tb1.checked) tb1.click( );
                if (tb2.checked) tb2.click( );
                if (tb3.checked) tb3.click( );">
</form>


Library Navigation Links

Copyright © 2003 O'Reilly & Associates. All rights reserved.