Article

DHTML Utopia: Modern Web Design Using JavaScript & DOM

Page: 1 2 3 4

Chapter 4. Detecting Browser Features
You just listed all my best features.

--The Cat, Red Dwarf, Series 3, Episode DNA

An important design constraint when adding DHTML to your Websites is that it should be unobtrusive. By "unobtrusive," I mean that if a given Web browser doesn't support the DHTML features you're using, that absence should affect the user experience as little as possible. Errors should not be shown to the user: the site should be perfectly usable without the DHTML enhancements. The browsers that render your site will fall into the following broad categories:

  1. Offer no JavaScript support at all, or have JavaScript turned off.

  2. Provide some JavaScript support, but modern features are missing.

  3. Have full JavaScript support, but offer no W3C DOM support at all.

  4. Provide incomplete DOM support, but some DOM features are missing or
    buggy.

  5. Offer complete DOM support without bugs.

The first and the last categories hold no concerns for you as a DHTML developer. A browser that does not run JavaScript at all will simply work without calling any of your DHTML code, so you can ignore it for the purposes of this discussion. You just need to make sure that your page displays correctly when JavaScript is turned off. (For example, if your DHTML shows and hides some areas of the page, those areas should show initially, then be hidden with DHTML, so that they are available to non-DHTML browsers.) Similarly, a browser that implements the DOM completely and without bugs would make life very easy. It's a shame that such browsers do not exist.

The three categories in the middle of the list are of concern to us in this chapter. Here, we'll explore how to identify which DHTML features are supported by a given browser before we try to utilize those features in running our code.

There are basically two ways to working out whether the browser that's being used supports a given feature. (Actually, there's a third way to identify browser support. The DOM standards specify a document.implementation.hasFeature method that you can use to detect DOM support. It's rarely used, though.) The first approach is to work out which browser is being used, then have a list within your code that states which browser supports which features. The second way is to test for the existence of a required feature directly. In the following discussion, we'll see that classifying browsers by type isn't as good as detecting features on a case-by-case basis.

Old-Fashioned Browser Sniffing

In the bad old days, before browser manufacturers standardized on the DOM, JavaScript developers relied on detection of the browser's brand and version via a process known as browser sniffing. Each browser provides a window.navigator object, containing details about the browser, which can be checked from JavaScript. We can, for example, find the name of the browser (the "user agent string") as follows:

var browserName = navigator.userAgent;    
var isIE = browserName.match(/MSIE/); // find IE and look-alikes

Don't do this any more! This technique, like many other relics from the Dark Ages of JavaScript coding (before the W3C DOM specifications appeared), should not be used. Browser sniffing is flaky and prone to error, and should be avoided like the black plague. Really: I'm not kidding here.

Why am I so unenthusiastic about browser sniffing? There are lots of reasons. Some browsers lie about, or attempt to disguise, their true details; some, such as Opera, can be configured to deliver a user agent string of the user's choice. It's pretty much impossible to stay up-to-date with every version of every browser, and it's definitely impossible to know which features each version supported upon its release. Moreover, if your site is required to last for any reasonable period of time, new browser versions will be released after your site, and your browser-sniffing code will be unable to account for them. Browser sniffing—what little of it remains—should be confined to the dustbin of history. Put it in the "we didn't know any better" category. There is a significantly better method available: feature sniffing.

Modern DOM Feature Sniffing

Instead of detecting the user's browser, then working out for yourself whether it supports a given feature, simply ask the browser directly whether it supports the feature. For example, a high proportion of DHTML scripts use the DOM method getElementById. To work out whether a particular visitor's browser supports this method, you can use:

if (document.getElementById) {    
 // and here you know it is supported    
}

If the if statement test passes, we know that the browser supports the feature in question. It is important to note that getElementById is not followed by brackets! We do not say:

if (document.getElementById())

If we include the brackets, we call the method getElementById. If we do not include the brackets, we're referring to the JavaScript Function object that underlies the method. This is a very important distinction. Including the brackets would mean that we were testing the return value of the method call, which we do not want to do. For a start, this would cause an error in a non-DOM browser, because we can't call the getElementById method there at all—it doesn't exist! When we test the Function object instead, we're assessing it for existence. Browsers that don't support the method will fail the test. Therefore, they will not run the code enclosed by the if statement; nor will they display an error.

This feature of JavaScript—the ability to test whether a method exists—has been part of the language since its inception; thus, it is safe to use it on even the oldest JavaScript-supporting browsers. You may recall from the previous chapter the technique of referring to a Function object without calling it. In Chapter 3, Handling DOM Events, we used it to assign a function as an event listener without actually calling it. In JavaScript, everything can be treated as an object if you try hard enough; methods are no exception!

Which DOM Features Should We Test?

The easiest approach is to test for every DOM method you intend to use. If your code uses getElementById and createElement, test for the existence of both methods. This will cover browsers in the fourth category above: the ones that implement some—but not all—of the DOM.

It is not reasonable to assume that a browser that supports getElementById also supports getElementsByTagName. You must explicitly test for each feature.

Where Should We Test for DOM Features?

An easy way to handle these tests is to execute them before your DHTML sets up any event listeners. A large subset of DHTML scripts work by setting on page load some event listeners that will be called as various elements in the browser fire events. If, before setting up the event listeners, you check that the browser supplies all the DOM features required by the code, event listeners will not be set up for browsers that do not support those features. You can therefore reasonably assume in setting up your event listeners that all the features you require are available; this assumption can simplify your code immensely. Here's an example:

function myScriptInit() {    
 if (!document.getElementById ||    
     !document.getElementsByTagName ||    
     !document.createElement) {    
   return;    
 }    
 // set up the event listeners here    
}    
   
function myScriptEventListener() {    
 var foo = document.getElementById('foo');  // safe to use    
}    
   
addEvent(window, 'load', myScriptInit, false);

This script contains a myScriptInit function, which sets up myScriptEventListener as an event listener. But, before we set up that listener, we check for the existence of the DOM methods getElementById, getElementsByTagName, and createElement.

The if statement says: "if the JavaScript Function object document.getElementById does not exist, or if the Function object document.getElementsByTagName does not exist, or if the Function object document.createElement does not exist, exit the myScriptInit function." This means that, should any of those objects not be supported, the myScriptInit function will exit at that point: it will not even get as far as setting up the event listeners. Our code will set up listeners only on browsers that do support those methods. Therefore, as above, the listener function myScriptEventListener can feel safe in using document.getElementById without first checking to ensure that it is supported. If it wasn't supported, the listener function would not have been set up.

All this sniffing relies on JavaScript's runtime behavior. Even though the scripts are read by the browser at load time, no checks are done on the objects stated in the scripts until the code is run. This allows us to put browser objects in all scripts, and use them only when our detection code gets around to it: an arrangement called late binding.

Testing Non-DOM Features

Feature sniffing can be used on any JavaScript object: not just methods, and not just those methods that are part of the DOM. Commonly used examples are the offset properties (offsetWidth, offsetHeight, offsetLeft and offsetTop) of an element. These JavaScript properties are an extension to the DOM provided by all the major browsers. They return information on the size and position of an element in pixels. We can test whether those properties are defined on a given element's object as follows:

var foo = document.getElementById('foo');    
   
if (typeof foo.offsetHeight != 'undefined') {    
 var fooHeight = foo.offsetHeight;    
}

Here, we set fooHeight if, and only if, offsetHeight is supported on foo. This is a different type of check from the method we used before, though: isn't it possible simply to say, if (foo.offsetHeight)? This isn't a good approach to use. If foo.offsetHeight is not defined, if (foo.offsetHeight) will not be true, just as we expect. However, the if statement will also fail if foo.offsetHeight does exist, but is equal to 0 (zero). This is possible because JavaScript treats zero as meaning false. Testing whether a given item is defined just got a little more complex (but only a little!).

If you are testing for the existence of function functionName, or method methodName (on an object obj), use the function/method name without the brackets to do so:

if (functionName) { ... }    
if (obj.methodName) { ... }

Likewise, if you're testing for a variable v, or for a DOM property prop of an object, you can often use the variable or the DOM attribute's property name directly:

if (v) { ... }    
if (obj.prop) { ... }

But, watch out! If the variable or property contains numbers or strings (as does offsetHeight, for example) then use typeof, because a number might be 0 (zero), and a string might be the empty string "", both which also evaluate to false:

if (typeof v != 'undefined') { ... }    
if (typeof obj.prop != 'undefined') { ... }

Sniffing at Work: scrollImage

Lots of Websites contain photo galleries: pages listing thumbnails of photographs that, when clicked on, display the photos at full size. An interesting enhancement to such a site might be to let the user see the full-size photo without having to click to load it. When the user mouses over the thumbnail, that thumbnail could become a "viewing area" in which a snippet of the full-sized image is shown. This technique is useful if your thumbnails aren't detailed enough to enable users to tell the difference between superficially similar images. It's especially handy if your thumbnails display something like a document, rather than a photo. Figure 4.1 shows the final effect:

Figure 4.1. The thumbnail display implemented by the scrollImage example.

1478_ScrollImage.

We'll describe what's going on here in a moment. We'll review the code first, then see a demonstration before we get to the explanation.

Setting Up the Page

The HTML file for this technique is straightforward:

Example 4.1. scrollImage.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"    
   "http://www.w3.org/TR/html4/strict.dtd">    
<html>    
 <head>    
   <title>ScrollImage demonstration</title>    
   <script src="scrollImage.js" type="text/javascript"></script>    
   <style type="text/css">    
     .scrollimage {    
       display: block;    
       float: left;    
       border: 1px solid black;    
       margin: 1em;    
       padding: 0;    
     }    
   
     .scrollimage:hover {    
       position: relative;    
     }    
   
     .scrollimage img {    
       border: none;    
     }    
   
     .scrollimage:hover img {    
       display: none;    
     }    
   </style>    
 </head>    
 <body>    
   
   <h1>Scanned documents</h1>    
   
   <p>    
     <a href="1.jpg" class="scrollimage"    
        mainx="563" mainy="823" thumbx="82" thumby="120"    
        style="background: url(1.jpg); width: 82px;    
        height: 120px;"    
     ><img src="1-thumb.jpg"></a>    
   
     <a href="2.jpg" class="scrollimage"    
        mainx="563" mainy="777" thumbx="87" thumby="120"    
        style="background: url(2.jpg); width: 87px;    
        height: 120px;"    
     ><img src="2-thumb.jpg"></a>    
   
     <a href="3.jpg" class="scrollimage"    
        mainx="567" mainy="823" thumbx="83" thumby="120"    
        style="background: url(3.jpg); width: 83px;    
        height: 120px;"    
     ><img src="3-thumb.jpg"></a>    
   
     <a href="4.jpg" class="scrollimage"    
        mainx="558" mainy="806" thumbx="83" thumby="120"    
        style="background: url(4.jpg); width: 83px;    
        height: 120px;"    
     ><img src="4-thumb.jpg"></a>    
   
     <a href="5.jpg" class="scrollimage"    
        mainx="434" mainy="467" thumbx="112" thumby="120"    
        style="background: url(5.jpg); width: 112px;    
        height: 120px;"    
     ><img src="5-thumb.jpg"></a>    
   </p>    
   
 </body>    
</html>/#pc#/    
   
The content of this page is fairly obvious. Notice how the image elements are hidden by CSS styles when the mouse moves over them. This page also includes—with the <script src="scrollImage.js" type="text/javascript"></script> line—this JavaScript file:    
   
Example 4.2. scrollImage.js    
   
/#pc#/// Based on findPos*, by ppk    
// (http://www.quirksmode.org/js/findpos.html)    
function findPosX(obj) {    
 var curLeft = 0;    
 if (obj.offsetParent) {    
   do {    
     curLeft += obj.offsetLeft;    
   } while (obj = obj.offsetParent);    
 }    
 else if (obj.x) {    
   curLeft += obj.x;    
 }    
 return curLeft;    
}    
   
function findPosY(obj) {    
 var curTop = 0;    
 if (obj.offsetParent) {    
   do {    
     curTop += obj.offsetTop;    
   } while (obj = obj.offsetParent);    
 }    
 else if (obj.y) {    
   curTop += obj.y;    
 }    
 return curTop;    
}    
   
// cross-browser event handling for IE5+, NS6+ and Mozilla/Gecko    
// By Scott Andrew    
function addEvent(obj, evType, fn, useCapture) {    
 if (obj.addEventListener) {    
   obj.addEventListener(evType, fn, useCapture);    
   return true;    
 } else if (obj.attachEvent) {    
   var r = obj.attachEvent('on' + evType, fn);    
   return r;    
 } else {    
   obj['on' + evType] = fn;    
 }    
}    
   
addEvent(window, 'load', scrollInit, false);    
   
function scrollInit() {    
 if (!document.getElementsByTagName)    
   return;    
 var allLinks = document.getElementsByTagName('a');    
 for (var i = 0; i < allLinks.length; i++) {    
   var link = allLinks[i];    
   if ((' ' + link . className + ' ').indexOf(' scrollimage ') !=    
       -1) {    
     addEvent(link, 'mousemove', moveListener, false);    
   }    
 }    
}    
   
function attVal(element, attName) {    
 return parseInt(element.getAttribute(attName));    
}    
   
function moveListener(ev) {    
 var e = window.event ? window.event : ev;    
 var t = e.target ? e.target : e.srcElement;    
   
 var xPos = e.clientX - findPosX(t);    
 var yPos = e.clientY - findPosY(t);    
   
 if (t.nodeName.toLowerCase() == 'img')    
   t = t.parentNode;    
 if (t.nodeName.toLowerCase() == 'a') {    
   
   // scaleFactorY = (width(big) - width(small)) / width(small)    
   var scaleFactorY =    
       (attVal(t, 'mainy') - attVal(t, 'thumby')) / attVal(t,    
       'thumby');    
   var scaleFactorX =    
       (attVal(t, 'mainx') - attVal(t, 'thumbx')) / attVal(t,    
       'thumbx');    
   
   t.style.backgroundPosition =    
       (-parseInt(xPos * scaleFactorX)) + 'px ' +    
       (-parseInt(yPos * scaleFactorY)) + 'px';    
 }    
}

We'll explore (and fix!) this code shortly. Finally, the page also contains images: five at full-size, and five thumbnails. You can find them in the code archive for this book.

Demonstrating the DHTML Effect

Let's see how the page works. The HTML document shows five images as thumbnails; in this example, they're thumbnails of individual pages of a scanned-in document. Figure 4.2 shows the page content under normal circumstances.

Figure 4.2. Thumbnails of a document.

1478_Thumbnails

When we mouse-over a thumbnail image, though, the display of that thumbnail changes to show the actual image to which it's linked, as shown in Figure 4.3.

The thumbnail becomes a viewing area in which we can see a snippet of the full-size image. As the cursor moves over the third image, we see the content of the third image at full size through the viewing area. For a document thumbnail such as this, we can use the cursor to move around the document within the viewing area, so that we can read the content and see if it's the document we want. This technique can also be useful, as mentioned, in photo galleries containing images that look similar when displayed at thumbnail size.

Figure 4.3. Mousing over a thumbnail.

1478_MousingThumb

How the Code Works

Conceptually, the code works as follows: we set up the page so that every "scrollable" image is made up of an <a> tag of class scrollimage, which contains an <img> tag displaying the thumbnail. We apply the full-size image as the CSS background image of the <a> tag. Then, when the user mouses over the a element, we hide the img element entirely, allowing the a element's background image to show through. We then manipulate the position of that background image so that it moves in accordance with the cursor. (We're storing the dimensions of the larger image in custom attributes on the a element: mainx, mainy, thumbx, and thumby. This is a slightly suspect technique: it will prevent the HTML from validating, and should therefore be approached with caution. In this case, however, it is the easiest way to tie the required values to each of the a elements.)

This is all fairly advanced stuff, so we need to confirm that the running browser supports all the features we need in order to make it work. We start by making the script initialize on page load with the line:

Example 4.3. scrollImage.js (excerpt)

addEvent(window, 'load', scrollInit, false);/#pc#/    
   
We saw the addEvent method in Chapter 3, Handling DOM Events, but, with what we've learned about feature detection, its workings should now be much clearer to you. First, we check for the existence of an addEventListener method on the passed object, to see if the user's browser supports the DOM Events model correctly:    
   
Example 4.4. scrollImage.js (excerpt)    
   
/#pc#/function addEvent(obj, evType, fn, useCapture) {    
 if (obj.addEventListener) {    
   obj.addEventListener(evType, fn, useCapture);    
   return true;

Failing that, we look for Internet Explorer's proprietary attachEvent method on the object.

Example 4.5. scrollImage.js (excerpt)

} else if (obj.attachEvent) {    
   var r = obj.attachEvent('on' + evType, fn);    
   return r;

Failing that, we attach the event listener directly to the element, as an event handler; this is required for IE5 on Macintosh.

Example 4.6. scrollImage.js (excerpt)

} else {    
   obj['on' + evType] = fn;    
 }

This procedure caters for all the ways by which we might attach an event listener, using feature sniffing to see which option is available.

The initialization function that sets up the scrolling effect, scrollInit, uses document.getElementsByTagName to find all the a elements in the document. Therefore, scrollInit checks for this method's existence before proceeding:

Example 4.7. scrollImage.js (excerpt)

function scrollInit() {    
 if (!document.getElementsByTagName)    
   return;

If the user's browser doesn't support document.getElementsByTagName, then we return from the scrollInit function and don't progress any further.

One extra trick in the feature sniffing code, as described in Chapter 3, Handling DOM Events, addresses the way in which we find the event object when we're inside the moveListener event listener. As we know, the DOM Events specification mandates that an event object is passed to the event listener as an argument, whereas Internet Explorer makes the event object available as the global window.event. So, our code checks for the existence of window.event, and uses it as the event object if it exists; the code falls back to the passed-in argument if window.event is not present:

Example 4.8. scrollImage.js (excerpt)

function moveListener(ev) {    
 var e = window.event ? window.event : ev;

Next, we need to get the event's target from that event object; the DOM specifies e.target, and Internet Explorer provides e.srcElement. Another feature-sniff gives us the appropriate value:

Example 4.9. scrollImage.js (excerpt)

var t = e.target ? e.target : e.srcElement;

This is a compressed, shorthand version of the code we saw in Chapter 3, Handling DOM Events.

The next step is for the code to get the position of the mouse inside the thumbnail image area. This is the code from the full listing above that is supposed to do this:

var xPos = e.clientX - findPosX(t);    
 var yPos = e.clientY - findPosY(t);

In theory, e.clientX and e.clientY give the x- and y-coordinates of the mouse within the browser window, respectively. By subtracting from these the x- and y-coordinates of the target element, we obtain the mouse's position within that element.

Depending on your browser of choice, this might seem to work just fine at first glance. Peter-Paul Koch's findPosX and findPosY functions make short work of getting the target element's position. (For a complete description of how findPosX and findPosY work, visit Peter-Paul Koch's page on the subject.) Unfortunately, the clientX and clientY properties of the event object are nowhere near as reliable.

clientX and clientY Problems

The code above is flawed: the event listener uses e.clientX and e.clientY to ascertain the position of the mouse.

But that's not a flaw, is it? After all, it's in the DOM specifications!

Well, it's sort of a flaw—a flaw in the way browser manufacturers interpret the specification. Peter-Paul Koch studies this problem in great detail in his comprehensive article, Mission Impossible—Mouse Position. The problem occurs only when the page is scrolled (which was not the case with the above page). When a page is scrolled, the specification is rather vague on whether clientX and clientY are returned relative to the whole document, or to the window (the part of the document that is visible). Internet Explorer returns them relative to the window, as does Mozilla, but all of Opera, Konqueror, and iCab return them relative to the document. Netscape also provides pageX and pageY, which are mouse coordinates relative to the document. (Ironically enough, Internet Explorer may be the only browser which is fully compliant with the standard; the best reading of the specification is that clientX and clientY should be relative to the window.)

So, we need to use pageX and pageY if they exist, and clientX and clientY if they do not; if we're in Internet Explorer, however, we have to add to clientX and clientY the amounts by which the page has been scrolled. But how do we know if we're in Internet Explorer? We use browser detection.

Browser Detection You Can't Avoid

That spluttering noise you can hear in the background is the crowd rightly pointing out that we consigned browser detection to the dustbin of history only a few pages back, and they're not wrong. However, there are occasions when different browsers implement the same properties (in this case, clientX and clientY) in different ways and when there are no other objects available for sniffing that can us tell which of the different implementations is in use.

On such occasions, there is no alternative but to use the dreaded browser sniffing to work out what to do. The mouse position issue described here is almost the only such situation. The very thought that it might be necessary to use browser detection should make all right-thinking DHTML developers shudder with guilt, but, sadly, there's nothing for it! We add the browser detection script to the code just before we call addEvent to set up our window load listener:

Example 4.10. scrollImage.js (excerpt)

var isIE = !window.opera && navigator.userAgent.indexOf('MSIE') !=    
   -1;

Note that, first, we check that window.opera is false or non-existent; Opera sets this variable to make it easy for scripts to detect that it is the browser in use (Opera also implements user-agent switching, so that, from a navigator.userAgent perspective, it can appear to be Internet Explorer). Once we've established that we're not using Opera, we go on to look for "MSIE" in the user agent string; if this is present, Internet Explorer is the browser in use.

Our updated moveListener event listener now looks like this:

Example 4.11. scrollImage.js (excerpt)

function moveListener(ev) {    
 var e = window.event ? window.event : ev;    
 var t = e.target ? e.target : e.srcElement;    
   
 var mX, mY;    
 if (e.pageX && e.pageY) {    
   mX = e.pageX;    
   my = e.pageY;    
 } else if (e.clientX && e.clientY) {    
   mX = e.clientX;    
   mY = e.clientY;    
   if (isIE) {    
     mX += document.body.scrollLeft;    
     mY += document.body.scrollTop;    
   }    
 }    
   
 var xPos = mX - findPosX(t);    
 var yPos = mY - findPosY(t);    
   
// ... the rest as before ...

Note that we check first for pageX and pageY (for Mozilla), then fall through to clientX and clientY. We handle Internet Explorer by checking the isIE variable; if it's true, we add the document's scroll amounts as required. We're using the browser detect as little as possible; specifically, Netscape/Mozilla provide the pageX and pageY properties, and we look for them through feature sniffing, not by performing browser detection for Mozilla.

Calculating Screen Positions

The last section of our code has little to do with browser detects, but, having spent all this time to get the right X and Y coordinates, it makes sense to understand how to use them.

The last part of the moveListener function starts with a couple of ifs, which ensure that we have in hand a reference to the <a> tag surrounding the thumbnail <img> of interest. No surprises there, so we grab the required DOM element:

Example 4.12. scrollImage.js (excerpt)

if (t.nodeName.toLowerCase() == 'img')    
   t = t.parentNode;    
 if (t.nodeName.toLowerCase() == 'a') {

Next, we have the first of two sets of calculations:

Example 4.13. scrollImage.js (excerpt)

// scaleFactorY = (width(big) - width(small)) / width(small)    
   var scaleFactorY =    
       (attVal(t, 'mainy') - attVal(t, 'thumby')) / attVal(t,    
       'thumby');    
   var scaleFactorX =    
       (attVal(t, 'mainx') - attVal(t, 'thumbx')) / attVal(t,    
       'thumbx');

Code like this is liable to be specific to each DHTML effect you undertake, but the mind-bending you have to do to come up with the code is similar in all cases. Take a deep breath: here we go!

With the large background image showing through the viewing area, what should appear when the cursor is in the top-left corner of that viewing area? The top-left corner of the big image should be in the top-left corner of the viewing area: that's straightforward. Now, what should appear when the cursor is located at the bottom-right corner of the viewing area? Should the bottom-right corner of the full-sized image be in the top-left corner of the viewing area? That's what would happen if the big image were moved by its full size across the viewing area as the cursor was moved the full distance across the viewing area. Think about it carefully; you might like to try experimenting with two pieces of paper, one of which has a rectangular hole in it. The big image would eventually disappear off the top-left corner of the viewing area! If the background image were tiled (the default), additional copies of the image would be visible at this bottom-right corner—a very odd result.

We don't want the image to move that far. If we move the cursor to the extreme bottom-right of the viewing area, we want the big image to move by almost its entire size—but not quite! We want the bottom-right corner of the big image to move only as far as the bottom-right corner of the viewing area, and not move any further towards the top-left.

Now, to make the big image move, we have to calculate a distance by which to move it. Take some example figures: suppose the big image is ten times the size of the thumbnail. Let's suppose the image is 500 pixels on each side, and the thumbnail's 50 pixels on each side. For every pixel by which the cursor moves, the big image should move 500/50: ten times as fast. So the "scale factor" is ten. But, wait a minute! If the cursor moves 50 pixels left, the big image will move 500 pixels left: right off the left edge of the viewing area. That's too far. We want it to move at most 500 minus 50 pixels, so that it's always "inside" the viewing area. Therefore, the real scale factor is (500 – 50) / 50 = 9. The full-sized image should move nine times as fast as the cursor. That's what the first set of calculations does, except that it calculates scale factors in both dimensions, since most images are rectangles, not squares.

Next, we want to move the big image. Here's the second set of calculations:

Example 4.14. scrollImage.js (excerpt)

t.style.backgroundPosition =    
       (-parseInt(xPos * scaleFactorX)) + 'px ' +    
       (-parseInt(yPos * scaleFactorY)) + 'px';

Now, if (for example) we move the mouse from the top-left towards the bottom-right, we're scanning diagonally across the viewing area. As we move, we want new areas of the big image to come into view. So the big image had better slide in the opposite direction to the mouse: up towards, and beyond, the top left. It's like using a negative margin to bleed text to the left and top of a page. And that's what we do by calculating negative pixel amounts.

This idea may seem back-to-front initially. Think of it as though you were shooting a scene for a movie. The camera (the thumbnail viewing area) is fixed into place, so it must be the scene at which the camera points that moves if there's to be any panning effect. Alternately, imagine yourself looking out of the window of a moving train without turning your head. It's the same effect again, provided the train goes backwards!

Summary

In this chapter, we've learned that browsers don't always support all the DOM features we'd like, and discussed how feature sniffing helps us as DHTML developers to code defensively around this issue. Browser sniffing allows us to deliver dynamic features to browsers that can handle them and, at the same time, to avoid crashing or throwing errors in browsers that can't. We looked at the old method, browser sniffing, and explained why it shouldn't be used if at all possible. We then explored one occasion on which feature sniffing can't provide everything we need, leaving us the old method as a last resort.

That's it for this excerpt of DHTML Utopia: Modern Web Design Using JavaScript & DOM! What's next?

Download this chapter in PDF format, and you'll have a copy you can refer to at any time.

Review the book's table of contents to find out exactly what's included.

Buy your own copy of the book now, right here at SitePoint.com.

We hope you enjoy DHTML Utopia: Modern Web Design Using JavaScript & DOM.

If you liked this article, share the love:
Print-Friendly Version Suggest an Article

Sponsored Links

Rate This Article

  • 1
    Poor
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
    Great

Post A Comment

You need to be a member of the SitePoint Forums to comment on this post. Sign Up

Already a member? Post using your SitePoint Forums account: