At the moment I am writing an Introduction to Events. As part of my study I wanted to create a generic, simple script that detects the mouse coordinates at the time of the event. Because such a script can work in Netscape,Explorer, Opera, Konqueror and iCab, it should work in all these browsers.
But I failed. In fact the situation is so bad that I have reluctantly come to the concl <script src="http://hi.images.csdn.net/js/blog/tiny_mce/themes/advanced/langs/zh.js" type="text/javascript"></script> <script src="http://hi.images.csdn.net/js/blog/tiny_mce/plugins/syntaxhl/langs/zh.js" type="text/javascript"></script> usion that a cross–browser script for finding the mouse coordinates during an event must necessarily use a browser detect, something I truly loathe.
The problem is, believe it or not, that Explorer correctly follows the standard — or rather that Opera, Konqueror and iCab have en masse decided not to follow the standard — or maybe that the standard is in fact not the standard.
Detecting the mouse coordinates became such a complex matter, and the story contains such fundamental lessons about standardization, that I wrote this article.
In search of a reference point
But let’s begin at the beginning. We are looking for mouse coordinates.
The main question is “mouse coordinates relative to what?” If a mouse coordinate property has a value of 300 this always means “300 pixels from my reference point”, but it doesn’t tell us what this reference point is.
So first we have to decide which reference point we need and then search the properties to match from the six available pairs (see also my compatibility table).
Please note the difference between document and window. The window is the “viewport” the user has available to view the document in. The document is frequently far longer than the window; in that case we scroll it to get another portion of the document into our viewport.
--------------------- | Start of document | ------------------------- | | X | | | | | | |<--- Browser window -->| | | | | ------------------------- | | | End of document | ---------------------
For example, the coordinates of the X relative to the window would be 10,10 while its coordinates relative to the document would be 10,100.
What we need are the mouse coordinates relative to the document because we often want to place some kind of DHTML layer on or just next to the mouse position. DHTML layers also calculate their position relative to document. If we can find the mouse coordinates relative to the document we can immediately paste them into the top
and left
properties of the DHTML layer we wish to move, avoiding all kinds of trouble.
So where can we find these mouse coordinates relative to the document?
DOM implementation’s client area
The W3C DOM Level 2 Events standard specifies two property pairs for mouse coordinates: screenX/Y
, the mouse coordinates relative to the entire computer screen, and clientX/Y
, which are defined as
“the horizontal/vertical coordinate at which the event occurred relative to the DOM implementation’s client area.”
Now what, one wonders, is the “DOM implementation’s client area”? Because both Netscape 6 and Explorer define clientX/Y
as the mouse coordinates relative to the window, it is commonly translated as window.
This means that to obtain the mouse position relative to the document we have to add the values of some unstandardized, browser specific scrolling offset properties to the coordinates relative to the window clientX/Y
give us. Thus there is no standards compatible way to find this information. So far so bad, though it can be worked around.
The minor browsers
But it gets worse. Opera, Konqueror and iCab define clientX/Y
as the mouse coordinates relative to the document, not to the window. So three important minor browsers disagree with Netscape 6 and Explorer and even with W3C, though all three have a good name for standards compliance.
We don’t see such incompatibilities often because the minor browsers can’t afford them. Although Netscape/Explorer code branching has been common for many years, few web developers would bother to write special code branches for the minor browsers. If a certain method or property is supported by a minor browser it must be exactly equal to the Microsoft property of Explorer or the W3C property of Netscape 6. Incompatibility with both of the big ones means that neither code branch will work and that scripts will not function correctly. Thus incompatibility is uncommon, usually caused by a bug.
But now three of them disagree with both big ones in the same way. This is a rare sight, so rare in fact, that we should sit up and take notice. Might they have a point? It is clear that their implementation of clientX/Y
would greatly benefit web developers, if it were truly cross–browser.
Browser incompatibility
But it isn’t cross–browser, and in fact our chance at a properly written cross–browser script is destroyed. As we’ve seen Netscape 6 and Explorer supportclientX/Y
according to the standard: the properties contain the mouse coordinates relative to the window.
Netscape fortunately stores the coordinates relative to the document in another (non–standard) property pair, so we can avoid using clientX/Y
. Again we see that browser vendors are willing to help us find the mouse position relative to the document.
That leaves Explorer as the single browser that does not give us the information we need, as the single browser for which we must follow the W3C specification to the last dot and comma. Yet another rare sight.
The standards are not the standards?
Or do we all misunderstand the standard and should “DOM implementation’s client area” be translated as document? That would be a blessing to web developers — and restore the natural order of standards compliance in browser–land.
But if the standard means document it hides this fact very carefully. On the other hand, if it means window it is pretty vague, too. And we all know what happens when standards are vague. Browser vendors create their own standards. That’s what has happened here.
So let’s go down the sewer for some dirty fixes.
pageX, pageY
The old Netscape event model contains the pageX/Y
property pair, which gives the mouse coordinates relative to the entire document. Since this is exactly the information we’re looking for, we try to apply these properties.
pageX/Y
is supported by Netscape 4 and 6 and sometimes by iCab. So for the benefit of these browsers we start up a bit of object detection. Does the browser actually support these properties? If so, use them.
function doSomething(e) { var posx = 0; var posy = 0; if (!e) var e = window.event; if (e.pageX || e.pageY) { posx = e.pageX; posy = e.pageY; } ...
Not only have we made our script Netscape 4 compatible, we have also neatly evaded the clientX/Y
problem in Netscape 6.
clientX, clientY
If the browser doesn’t support pageX/Y
we have to use clientX/Y
. If your page does not need to scroll you can ignore all compatibility questions. After all when the page hasn’t scrolled the coordinates relative to the window are the same as those relative to the document.
You just do
... else if (e.clientX || e.clientY) { posx = e.clientX; posy = e.clientY; } // posx and posy contain the mouse position relative to the document // Do something with this information }
and you’re ready.
Browser detect
However, if the page can scroll we have some very serious problems. There are two options.
- If
clientX/Y
contain the coordinates relative to the document, we read them out and are ready. - If
clientX/Y
contain the coordinates relative to the window, we read them out, add the scrolling offset of the page and are ready.
But how do we know which of these two options we have to use? I studied the remaining four property pairs to see if they offer a way out, but they don’t. I looked at the problem from a theoretical point of view: is it possible to ascertain the meaning of clientX/Y
by reading out some screen properties like scrolling offset and window height and performing complex calculations? No go.
Therefore I’m forced to use a browser detect. I loathe it, but there is no other way. So let’s hold our noses and do it.
var isOpera = (navigator.userAgent.indexOf('Opera') != -1); var isIE = (!isOpera && navigator.userAgent.indexOf('MSIE') != -1)
Remember that Opera can try to disguise itself as Explorer, which would be fatal to this script. Therefore we have to check for Opera first. Konqueror and iCab can disguise themselves too, by the way, but when they do they completely switch identity and are undetectable. Too Bad, cannot be helped.
Now we do
... else if (e.clientX || e.clientY) { posx = e.clientX; posy = e.clientY; if (isIE) { posx += document.body.scrollLeft; posy += document.body.scrollTop; } } // posx and posy contain // the mouse position relative to the document // Do something with this information }
and the script works, for the moment.
(Note that if you use a DOCTYPE in Explorer 6, you may have to search for scrollTop
in document.documentElement
instead of document.body
. I’m not yet sure of the details of this problem and in any case they are beyond the scope of this article.)
Drawbacks of a browser detect
This script is an interesting example of the drawbacks of browser detection as a regular programming technique. At the moment our dirty fix works fine for undisguised Explorer, Opera, Konqueror and iCab browsers, sure. But what about the future?
If any of the four browsers changes its clientX/Y
implementation the script doesn’t work correctly any more — and if Explorer switched it would be downright disastrous.
You could of course rewrite your browser detect, but do you remember every single page you used the script on? And how about earlier versions of the browser that has changed? You’d have to write some more code to correctly draw the line between standard–compatible and non–standard–compatible versions. And would these versions be the same on all operating systems?
For such reasons I detest the impossible situation W3C’s vagueness and the browser vendors’ well–meaning but untimely intervention have created. I want to keep my code clean, but thanks to the mishandling of clientX/Y
I have no choice but to accept the ugly navigator.userAgent
mess.
The clientX/Y
confusion is a blot on the otherwise excellent DOM Level 2 Events spec. Let’s hope for a speedy solution.