Section 22.1. Scripting Images


22.1. Scripting Images

Web pages include images that use the HTML <img> tag. Like all HTML elements, an <img> tag is part of the DOM and can therefore be scripted like any other element in a document. This section illustrates common techniques.

22.1.1. Images and the Level 0 DOM

Images were one of the first scriptable HTML elements, and the Level 0 DOM allows you to access them through the images[] array of the Document object. Each element of this array is an Image object that represents one <img> tag in the document. You can find complete documentation of the Image object in Part IV. Image objects can also be accessed with Level 1 DOM methods such as getElementById( ) and getElementsByTagName( ) (see Chapter 15).

The document.images[] array lists Image objects in the order in which they appear in the document. More usefully, however, it provides access to named images. If an <img> tag has a name attribute, the image can be retrieved using the name specified by that attribute. Consider this <img> tag, for example:

 <img name="nextpage" src="/books/2/427/1/html/2/nextpage.gif"> 

Assuming that no other <img> tag has the same value for its name attribute, the corresponding Image object is available as:

 document.images.nextpage 

or as:

 document.images["nextpage"] 

If no other tag (of any type) in the document shares the same name attribute, the Image object can even be accessed through a property of the document object itself:

 document.nextpage 

22.1.2. Traditional Image Rollovers

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.

The ability to dynamically replace one image with another in an HTML document opens the door to any number of special effects, from animation to digital clocks that update themselves in real time. 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. (To prevent jarring visual effects, the new image should be the same size as the original.) 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.[*] This simple HTML fragment displays an image within an <a> tag and uses JavaScript code in the onmouseover and onmouseout event handlers to create a rollover effect:

[*] No discussion of image rollovers is complete without pointing out that they can also be implemented using the CSS :hover pseudoclass to apply different CSS background images to elements when the mouse "hovers" over them. Unfortunately, making CSS image rollovers work portably is difficult. In practice, :hover is more useful when applied to hyperlinks containing text, rather than images.

 <a href="help.html"    onmouseover="document.helpimage.src='/books/2/427/1/html/2/images/help_rollover.gif';"    onmouseout="document.helpimage.src='/books/2/427/1/html/2/images/help.gif';"> <img name="helpimage" src="/books/2/427/1/html/2/images/help.gif" border="0"> </a> 

Note that in this code fragment, the <img> tag has a name attribute that makes it easy to refer to the corresponding Image object in the event handlers of the <a> tag. The border attribute prevents 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. These event handers are placed on the <a> tag for the benefit of very old browsers that support those handlers only on specific tags, such as <a>. In virtually every browser deployed today, you can also put the event handlers on the <img> tag itself, which simplifies the image lookup. The event-handler code can then refer to the Image object with the this keyword:

 <img src="/books/2/427/1/html/2/images/help.gif"     onmouseover="this.src='/books/2/427/1/html/2/images/help_rollover.gif'"     onmouseout="this.src='/books/2/427/1/html/2/images/help.gif'"> 

Image rollovers are strongly associated with clickability, so this <img> tag should still be enclosed in an <a> tag or given an onclick event handler.

22.1.3. Offscreen Images and Caching

In order to be viable, image rollovers and related effects need to be responsive. This means that you need some way to ensure that the necessary images are "prefetched" into the browser's cache. To force an image to be cached, you first create an Image object using the Image( ) constructor. Next, load an image into it by setting the src property of this object to the desired URL. This image is not added to the document, so it does not become visible, but the browser nevertheless loads and caches the image data. Later, when the same URL is used for an onscreen image, it can be quickly loaded from the browser's cache, rather than slowly loaded over the network.

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

 <script>(new Image( )).src = "images/help_rollover.gif";</script> <img src="/books/2/427/1/html/2/images/help.gif"     onmouseover="this.src='/books/2/427/1/html/2/images/help_rollover.gif'"     onmouseout="this.src='/books/2/427/1/html/2/images/help.gif'"> 

22.1.4. Unobtrusive Image Rollovers

The image rollover code just shown requires one <script> tag and two JavaScript event-handler attributes to implement a single rollover effect. This is a perfect example of obtrusive JavaScript. Although it is common to see code that mixes presentation (HTML) with behavior (JavaScript) like this, it is better to avoid it when you can. Especially when, as in this case, the amount of JavaScript code is so large that it effectively obscures the HTML. As a start, Example 22-1 shows a function that adds a rollover effect to a specified <img> element.

Example 22-1. Adding a rollover effect to an image

 /**  * Add a rollover effect to the specified image, by adding event  * handlers to switch the image to the specified URL while the  * mouse is over the image.  *  * If the image is specified as a string, search for an image with that  * string as its id or name attribute.  *  * This method sets the onmouseover and onmouseout event-handler properties  * of the specified image, overwriting and discarding any handlers previously  * set on those properties.  */ function addRollover(img, rolloverURL) {     if (typeof img == "string") {  // If img is a string,         var id = img;              // it is an id, not an image         img = null;                // and we don't have an image yet.         // First try looking the image up by id         if (document.getElementById) img = document.getElementById(id);         else if (document.all) img = document.all[id];         // If not found by id, try looking the image up by name.         if (!img) img = document.images[id];         // If we couldn't find the image, do nothing and fail silently         if (!img) return;     }     // If we found an element but it is not an <img> tag, we also fail     if (img.tagName.toLowerCase( ) != "img") return;     // Remember the original URL of the image     var baseURL = img.src;     // Preload the rollover image into the browser's cache     (new Image( )).src = rolloverURL;     img.onmouseover = function( ) { img.src = rolloverURL; }     img.onmouseout = function( ) { img.src = baseURL; } } 

The addRollover( ) function defined in Example 22-1 is not completely unobtrusive because in order to use it, you must still include a script in your HTML files that invokes the function. To achieve the goal of truly unobtrusive image rollovers, you need a way to indicate which images have rollovers and what the URL of the rollover image is, without resorting to JavaScript. One simple way to do so is to include a fake HTML attribute on the <img> tags. For example, you might code images that have rollover effects like this:

 <img src="/books/2/427/1/html/2/normalImage.gif" rollover="rolloverImage.gif"> 

With an HTML coding convention like this, you can easily locate all images that require rollover effects and set up those effects with the initRollovers( ) function defined in Example 22-2.

Example 22-2. Adding rollover effects unobtrusively

 /**  * Find all <img> tags in the document that have a "rollover"  * attribute on them.  Use the value of this attribute as the URL of an  * image to be displayed when the mouse passes over the image and set  * appropriate event handlers to create the rollover effect.  */ function initRollovers( ) {     var images = document.getElementsByTagName("img");     for(var i = 0; i < images.length; i++) {         var image = images[i];         var rolloverURL = image.getAttribute("rollover");         if (rolloverURL) addRollover(image, rolloverURL);     } } 

All that remains is to ensure that this initRollovers( ) method is invoked when the document has loaded. Code like the following should work in current browsers:

 if (window.addEventListener)     window.addEventListener("load", initRollovers, false); else if (window.attachEvent)     window.attachEvent("onload", initRollovers); 

See Chapter 17 for a more complete discussion of onload handling.

Note that if you place the addRollover( ) function and the initRollovers( ) function in the same file as the event-handler registration code, you have a completely unobtrusive solution for image rollovers. Simply include the file of code with a <script src=> tag, and place rollover attributes on any <img> tags that need rollover effects.

If you don't want your HTML files to fail validation because you've added a nonstandard rollover attribute to your <img> tags, you can switch to XHTML and use XML namespaces for the new attribute. Example 22-3 shows a namespace-aware version of the initRollovers( ) function. Note, however, that this version of the function does not work in Internet Explorer 6 because that browser does not support the DOM methods that support namespaces.

Example 22-3. Initializing rollovers with XHTML and namespaces

 /**  * Find all <img> tags in the document that have a "ro:src"  * attribute on them.  Use the value of this attribute as the URL of an  * image to be displayed when the mouse passes over the image, and set  * appropriate event handlers to create the rollover effect.  * The ro: namespace prefix should be mapped to the URI  * "http://www.davidflanagan.com/rollover"  */ function initRollovers( ) {     var images = document.getElementsByTagName("img");     for(var i = 0; i < images.length; i++) {         var image = images[i];         var rolloverURL = image.getAttributeNS(initRollovers.xmlns, "src");         if (rolloverURL) addRollover(image, rolloverURL);     } } // This is a made-up namespace URI for our "ro:" namespace initRollovers.xmlns = "http://www.davidflanagan.com/rollover"; 

22.1.5. Image Animations

Another reason to script the src property of an <img> tag is to perform an image animation in which an image is changed frequently enough that it approximates smooth motion. A typical application for this technique might be to display a series of weather maps that show the historical or forecast evolution of a storm system at hourly intervals over a two-day period.

Example 22-4 shows a JavaScript ImageLoop class for creating this kind of image animation. It demonstrates the same scripting of the src property and image prefetching techniques shown in Example 22-1. It also introduces the onload event handler of the Image object, which can determine when an image (or, in this case, a series of images) has finished loading. The animation code itself is driven by Window.setInterval( ) and is quite simple: simply increment the frame number and set the src property of the specified <img> tag to the URL of the next frame.

Here is a simple HTML file that uses this ImageLoop class:

 <head> <script src="/books/2/427/1/html/2/ImageLoop.js"></script> <script> var animation =     new ImageLoop("loop", 5,["images/0.gif", "images/1.gif", "images/2.gif",                              "images/3.gif", "images/4.gif", "images/5.gif",                              "images/6.gif", "images/7.gif", "images/8.gif"]); </script> </head> <body> <img  src="/books/2/427/1/html/2/images/loading.gif"> <button onclick="animation.start( )">Start</button> <button onclick="animation.stop( )">Stop</button> 

The code in Example 22-4 is somewhat more complicated than you might expect because both the Image.onload event handler and the Window.setInterval( ) timer function invoke functions as functions rather than as methods. For this reason, the ImageLoop( ) constructor must define nested functions that know how to operate on the newly constructed ImageLoop.

Example 22-4. Image animations

 /**  * ImageLoop.js: An ImageLoop class for performing image animations  *  * Constructor Arguments:  *   imageId:   the id of the <img> tag that will be animated  *   fps:       the number of frames to display per second  *   frameURLs: an array of URLs, one for each frame of the animation  *  * Public Methods:  *   start( ):   start the animation (but wait for all frames to load first)  *   stop( ):    stop the animation  *  * Public Properties:  *   loaded:    true if all frames of the animation have loaded,  *              false otherwise  */ function ImageLoop(imageId, fps, frameURLs) {     // Remember the image id. Don't look it up yet since this constructor     // may be called before the document is loaded.     this.imageId = imageId;     // Compute the time to wait between frames of the animation     this.frameInterval = 1000/fps;     // An array for holding Image objects for each frame     this.frames = new Array(frameURLs.length);     this.image = null;             // The <img> element, looked up by id     this.loaded = false;           // Whether all frames have loaded     this.loadedFrames = 0;         // How many frames have loaded     this.startOnLoad = false;      // Start animating when done loading?     this.frameNumber = -1;         // What frame is currently displayed     this.timer = null;             // The return value of setInterval( )     // Initialize the frames[] array and preload the images     for(var i = 0; i < frameURLs.length; i++) {         this.frames[i] = new Image( );      // Create Image object         // Register an event handler so we know when the frame is loaded         this.frames[i].onload = countLoadedFrames; // defined later         this.frames[i].src = frameURLs[i]; // Preload the frame's image     }     // This nested function is an event handler that counts how many     // frames have finished loading. When all are loaded, it sets a flag,     // and starts the animation if it has been requested to do so.     var loop = this;     function countLoadedFrames( ) {         loop.loadedFrames++;         if (loop.loadedFrames == loop.frames.length) {             loop.loaded = true;             if (loop.startOnLoad) loop.start( );         }     }     // Here we define a function that displays the next frame of the     // animation. This function can't be an ordinary instance method because     // setInterval( ) can only invoke functions, not methods. So we make     // it a closure that includes a reference to the ImageLoop object     this._displayNextFrame = function( ) {         // First, increment the frame number. The modulo operator (%) means         // that we loop from the last to the first frame         loop.frameNumber = (loop.frameNumber+1)%loop.frames.length;         // Update the src property of the image to the URL of the new frame         loop.image.src = loop.frames[loop.frameNumber].src;     }; } /**  * This method starts an ImageLoop animation. If the frame images have not  * finished loading, it instead sets a flag so that the animation will  * automatically be started when loading completes  */ ImageLoop.prototype.start = function( ) {     if (this.timer != null) return;   // Already started     // If loading is not complete, set a flag to start when it is     if (!this.loaded) this.startOnLoad = true;     else {         // If we haven't looked up the image by id yet, do so now         if (!this.image) this.image = document.getElementById(this.imageId);         // Display the first frame immediately         this._displayNextFrame( );         // And set a timer to display subsequent frames         this.timer = setInterval(this._displayNextFrame, this.frameInterval);     } }; /** Stop an ImageLoop animation */ ImageLoop.prototype.stop = function( ) {     if (this.timer) clearInterval(this.timer);     this.timer = null; }; 

22.1.6. Other Image Properties

In addition to the onload event handler demonstrated in Example 22-4, 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.

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.

The other properties of the Image object simply mirror the attributes of the HTML <img> tag. In modern browsers, these properties are read/write, and you can use JavaScript to dynamically change the size of an image, causing the browser to stretch or shrink it.




JavaScript. The Definitive Guide
JavaScript: The Definitive Guide
ISBN: 0596101996
EAN: 2147483647
Year: 2004
Pages: 767

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net