reptile7's JavaScript blog
Saturday, March 29, 2008
 
JavaScript Tom and Jerry, Part 2
Blog Entry #108

As promised, we will in today's post bring the 'cat chases the mouse' script of HTML Goodies' JavaScript Script Tip #91 into the modern era.

Let's start with the mymouse layer. As noted in Blog Entry #94, Netscape's support for the layer element/object began and ended with Netscape 4.x. You can replace the mymouse layer with a div element container if you want, but there's no reason why the animated kitten image itself can't be positioned and then moved around on the page. Accordingly, here's the CSS we'll use:

body { background-color: white; }
#mycat {
position: absolute;
top: 100px;
left: 100px;
border: 0px; }
/* mycat is the id value that we gave to the image in the previous entry. */

However, you probably won't experience any problems if you leave the mymouse layer in place. In the "Notes on invalid documents" section of Appendix B ("Performance, Implementation, and Design Notes") of the HTML 4.01 Specification, the W3C says, If a user agent encounters an element it does not recognize, it should try to render the element's content. A non-Netscape 4.x browser should consequently ignore the mymouse layer and then display normally the animated kitten image - both MSIE 5.1.6 and Netscape 7.02 do just that on my iMac.

You can also keep the script element's Netscape 4.x-specific code and prevent it from throwing errors if you conditionalize it as follows:

Enabling event capture:
if (document.layers) document.captureEvents(Event.MOUSEMOVE);

trackit( ) assignment statements:
if (document.layers) {
document.layers[0].pageX = ev.pageX;
document.layers[0].pageY = ev.pageY; }

As an object-detection if condition, document.layers returns true for Netscape 4.x and (to the best of my knowledge) false for all other browsers, which would therefore skip over the preceding commands.

So now, any Netscape 4.x users out there will still be able to see the script's effect after making the changes described below.

MSIE compatibility

Reference: For a comprehensive description of the MSIE event model, see the MSDN Library's "Understanding the Event Model" article.

In the previous post, we briefly discussed Netscape's event object interface, which has been adopted by the W3C. Internet Explorer utilizes a somewhat-but-not-dramatically different event object interface: in brief, when an event occurs, MSIE's JScript engine 'instantly updates' a pre-existing event object and makes it available to an associated event handler function, in which the keyword event* is used to reference the event object.
(*In the MSIE event model, the event object is actually a 'child object' of the window object, so you can reference the event object with window.event if you prefer.)

Microsoft's event object has clientX and clientY properties that are respectively similar to the pageX and pageY Netscape event object properties but differ therefrom in that they do not take page scrolling into account; for documents whose body content does not exceed the browser window's viewing area, as is the case for the Script Tip #91 Script, clientX/clientY and pageX/pageY are equivalent. The W3C has picked up the clientX/clientY properties (but not the pageX/pageY properties), as has Mozilla (Mozilla retains the pageX/pageY properties).

To make the Script Tip #91 Script work with MSIE, then, we can respectively assign event.clientX and event.clientY to the CSS left and top properties of the mycat image:

// For MSIE 4+:
document.all("mycat").style.left = event.clientX;
document.all("mycat").style.top = event.clientY;
// For MSIE 5+:
document.getElementById("mycat").style.left = event.clientX;
document.getElementById("mycat").style.top = event.clientY;

You may be thinking, "Let's use the name event for the Netscape event object, i.e.,

function trackit(event) {

and then we can use the above document.getElementById("mycat") statements with both MSIE and Netscape." Good idea in theory, but it doesn't work in practice: MSIE threw a 'clientX' is not an object runtime error when I tried this (Netscape executed the statements without incident). To avoid cross-browser errors, either set of MSIE event.clientX/event.clientY statements can be placed in an if (document.all) { ... } conditional, which will be triggered by MSIE 4+ but skipped over by Netscape.

For the MSIE association of the trackit( ) function with the user's mousemoves in the document content area, the script element's document.onmousemove = trackit statement can be used as-is.

Mozilla/W3C compatibility

We now consider current browsers that deploy the Netscape-cum-W3C event object interface. If we continue to use the variable ev for a user mousemove event, then the following trackit( ) code will take care of these browsers:

if (document.getElementById && !document.all) {
document.getElementById("mycat").style.left = ev.clientX;
document.getElementById("mycat").style.top = ev.clientY; }

I've taken the document.getElementById && !document.all condition from the "creating a context menu" example in JavaScript Kit's "Event handling in the DOM" tutorial; this condition should return true for all modern browsers except MSIE and Opera (Opera also supports the all collection).

Before moving on, I wanted to briefly comment on the use of if (window.Event) { ... } conditionals to flag Netscape in WebReference.com's "The Cross-Browser Event Model" column. I am hesitant to recommend this test because I don't know precisely what it tests for. Event (with a capital E) is not a reference for the event object that we've been variabilizing as ev; rather, Event is some sort of behind-the-scenes constructible core object: typeof Event returns function (FYI: typeof Array and typeof Date also return function) and document.write(Event) outputs a function Event() { [native code] } string. You won't find anything about Event, whatever it is, in any of Netscape's or Mozilla's materials; my bet is that, like the captureEvents( ) method, Event is now obsolete.

The addEventListener( ) method

The DOM Level 2 Events Specification introduces an addEventListener( ) method for registering an event handler on an object. The specification negligently does not define the term "event listener" in its "Terminology" section but generally seems to use "event listener" and "event handler" interchangeably (e.g., see this section). The addEventListener( ) syntax can be written as:

object.addEventListener("eventType", functionReference, captTorF)

For applying the addEventListener( ) method to the Script Tip #91 Script:

• The object would be document.

• The "eventType" is "mousemove", the type of event we are 'listening' for.

• The functionReference will be trackit (no parentheses), the function to be triggered by the user's mousemoves.

• As for the captTorF parameter...man, I was hoping to avoid a discussion of event flow/propagation in this post...to make a long story short, captTorF is set to true if we want a DOM ancestor of the "event target" - the source element at which the event occurs, which in this case is the body element - to capture the eventType event and false if we don't. Either true or false works for the Script Tip #91 Script: we want capture to occur at the document object level, but in the absence of capture, a user mousemove will spontaneously 'bubble up' from the body element to the document object (see the "Life Cycle of an Event" section of the aforecited MSDN Library reference; all mouse event types bubble, BTW).

A fly in the ointment: MSIE does not support the addEventListener( ) method but has introduced a similar attachEvent( ) method for registering event handlers:

document.attachEvent("onmousemove", trackit);

However, the Apple Developer Connection's "Supporting Three Event Models at Once" article, which I highly recommend, notes that the attachEvent( ) method works strictly in the IE5+/Windows environment (Mozilla's brief discussion of the attachEvent( ) method omits this crucial fact) and is thus off-limits to a Mac user such as myself (sure enough, the preceding command threw an Object doesn't support this property or method runtime error when I tried it out with MSIE on my computer).

Streamlining/maximizing cross-browser support

(1) The "Accommodating Both Event Object References" section of the aforementioned ADC "Supporting Three Event Models at Once" article provides a 'conditional template' via which we can streamline the above trackit( ) code for all browsers that support the getElementById( ) method as follows:

function trackit(ev) {
ev = ev ? ev : (event ? event : "");
if (ev) {
document.getElementById("mycat").style.left = ev.clientX;
document.getElementById("mycat").style.top = ev.clientY; } }
/* We previously discussed the ?: conditional operator in Blog Entry #101. */

The preceding function accommodates browsers supporting the W3C event model or the MSIE event model; in all cases, the ev variable is used to reference the event object.

(2) In its "Which Binding is Best?" section, the ADC article states, The [property assignment] approach [for event handler registration] is...supported in real life by all but the first-generation scriptable browsers. So we are probably better off sticking with the document.onmousemove = trackit statement, as opposed to using the addEventListener( ) method, for associating the trackit( ) function and the user's mousemoves with the document object.

A right-looking kitty

In Script Tip #91, Joe laments, Do you know how hard it was to find an animated kitty that is looking left? There are thousands of animated cats, but they all look right. Ugh! Joe wanted a left-looking kitty because he wanted the kitty to be looking at the mouse cursor, whose movement sets the position of the upper left-hand corner of the mymouse layer as explained in the previous entry. To transfer the script's effect to a right-looking kitty, the cursor would have to set the position of the upper right-hand corner of the kitty image or its container, and it turns out that this is very easy to do: simply subtract the image/container width from the mousemove clientX/pageX value in the trackit( ) function as shown below.

So, once again, put your mouse cursor in the div below and move it around:

Right-looking animated kitten gif


Meow! I've taken the above image

<img id="mycat" src="images/gersh02.gif" width="125" height="91" alt="Right-looking animated kitten gif" />

from Lisa's Free Original Animated Cat Gifs. The demo uses the following trackit( ) function:

function trackit(ev) {
ev = ev ? ev : (event ? event : "");
if (ev) {

if (!document.getElementById) { // For MSIE 4.x and Netscape 4.x
if (document.all) {
document.all("mycat").style.left = ev.clientX - 125;
document.all("mycat").style.top = ev.clientY; }
if (document.layers) {
document.layers[0].pageX = ev.pageX - 125;
document.layers[0].pageY = ev.pageY; } }

else {
document.getElementById("mycat").style.left = ev.clientX - 125;
document.getElementById("mycat").style.top = ev.clientY; } }

else window.alert("Sorry, your browser does not support the event object."); }

In the next entry, we'll move on to the Script Tips #92-93 Script, which is highlighted by the use of 'two-dimensional' arrays to build a series of tables that pop up in response to mouseover events.

reptile7

Comments: Post a Comment

<< Home

Powered by Blogger

Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)