Note: I have stopped updating this page. At this point nearly all popular browsers are have achieved a good level of compatibility on most of these features, and their behavior with respect to them just isn't changing much anymore. The only thing web designers really need to still watch out for is IE8, which is lingering due to the fact that it is the best version of IE that works on windows XP. |
The tests were done with the intention of learning just enough to write the code I needed to write. Many, many browsers have been tested over the years. The most recent test on each platform were done with the browser versions in the table below.
Windows | Macintosh | Linux | |
Internet Explorer | 9.0.8112.16421 | 5.2 | - |
Firefox | 5.0 (Gecko 5.0) |
5.0.1 (Gecko 5.0.1) |
4.0 (Gecko 2.0) |
Opera | 11.00 | 9.10 | 11.50 |
Safari | 5.1 (Webkit 534.50) |
5.0.2 (WebKit 533.18.1) |
- |
Chrome | 13.0.782.112 (Webkit 535.1) |
14.0.835.35 (Webkit 535.1) |
5.0.375.127 (Webkit 533.4) |
Epiphany | - | - | 2.28.2 (Webkit 531.2+) |
Konqueror | - | - | 4.3.1 |
Tests were also performed on Netscape, Mozilla, Camino, Seamonkey and K-Meleon. These all use the same layout engine as Firefox, called "Gecko" and behave the same way as versions of Firefox with the same Gecko version, just as Safari, Chrome, and Ephiphay all use the WebKit engine. In this article we will generally refer to Gecko and Webkit version numbers instead of any particular Gecko-based browser version numbers. See the Gecko/Webkit version table for the mapping between browser version numbers and layout engine version numbers.
This document is growing rapidly less imporant, as current versions of most leading browsers have converged on sensible and compatible behaviors. Internet Explorer still has deficiencies, but current versions of Firefox, Safari, Chrome and Opera are pretty consistant and sane for most things, though middle button mouse events still don't work in Firefox or Opera. The tables in this document have "correct" behavior highlighted in green.
Previous versions of this document included coverage of the iCab 3 browser, but more recent versions of iCab use WebKit, and so presumably behave exactly like the other WebKit browsers. Since it is unlikely that many web developers will want to go out of their way to support iCab 3, that material has been removed from this document and archived in a separate report on iCab 3.
The script used to collect the test results reported here is available at http://unixpapa.com/js/testmouse.html.
All browsers let you disable the default action of the left mouse button. I typically do this by ending the event handling function with the code below, which should cover all browsers. (Note that if you doing Level 0 event handling, that is not using addEventListener() to set up your event handlers, then just returning false is sufficient.)
if (event.preventDefault) event.preventDefault(); else event.returnValue= false; return false;
However, in some browsers the default actions of the right and middle mouse buttons cannot be disabled, or can only be disabled by changing browser settings. Such changes to browser settings typically effect all pages viewed through the browser, and typically cannot be done from Javascript. Since few users will want to do this, it pretty much means that the those mouse events are not usable from Javascript.
Netscape 4 pioneered this philosophy by making it impossible to disable the URL paste that occurs with a middle click, and requiring a config file edit to disable the context menus that come up with a right click. Firefox and the other Gecko browsers have released control of the right mouse button, but the default action of a middle click can not be disabled. You can change what the default action is by editing the middlemouse settings on the "about:config" URL, but Javascript can't cancel them.
The middle and right mouse buttons have had a long history of being broken in Opera. The right mouse button now works correctly by default, but has had a long history of being broken in different ways:
However, initially this option was over generous. If you set up a event handler on any part of the page (like on the link on my mouse event test page then the context menu would be disabled not only over that link, but over the entire page. Furthermore, only mousedown events were sent, never mouseup.
The middle mouse button in Opera is broken too. It triggers an events, but the default action cannot be disabled. No fiddling with the manner of its brokeness has occured so far.
None of the WebKit browsers seem to have any problem with this. All mouse buttons can be used on all platforms.
So here's the final score card:
Left Button | Middle Button | Right Button | |
Internet Explorer WebKit Konqueror |
Yes | Yes | Yes |
Gecko | Yes | No | Yes |
Opera ≥ 10.50 | Yes | No | Yes |
Opera < 7.23 Opera 8.5 - 9.63 |
Yes | No | No |
Opera 7.23 - 7.54 | Yes | No | Only if user has enabled
an obscure option |
The mousedown and mouseup events are meant mostly for situations where you depress the mouse button, move the mouse and then release it, say when a section of text is selected or when an object is dragged.
The click and dblclick events are meant mostly for situations where you are simply clicking on a link or a button. A click event normally occurs if there is a mousedown and a mouseup event on the same object. If you move the mouse to point to something else between mousedown and mouseup, then there will be no click event. Similarly, a dblclick event is triggered by two consecutive click events on the same object. object.
The contextmenu event is triggered on some browsers when you right click on something and it's default action is to bring up the browser's standard context menu. I think that in theory this event isn't really a mouse event. It might also be triggered by a keyboard shortcut. However it is usually associated with mouse clicks in practice.
It is important for programmers to remember that you cannot, in general, count on the full sequence of events firing. There is no guarantee that a mousedown will be followed by a mouseup, or that a mouseup event will be preceded by a mousedown event. If the user does a mousedown over the object you have set your event handler on, and then moves it away before releasing it, you will not see the mouseup event. If he moves it out of the browser window, there simply won't be one.
All Browsers | |
DOWN | mousedown |
UP | mouseup click |
So, here's what happens when you do click on the right mouse button:
Gecko ≥ 2.0 (Win) Internet Explorer Chrome ≥ 1.0 (Win) Opera ≥10.50 |
Gecko < 2.0 (Win) Gecko (Linux) Safari Chrome (Linux, Mac) |
Gecko (Mac) Konqueror |
Chrome 0.2 | Opera < 10.50 (default) |
Opera 7.23-7.54 (option enabled) |
Opera 8.0-9.63 (option enabled) |
Ephiphany 2.28 | |
DOWN | mousedown | mousedown contextmenu |
mousedown | mousedown | - | mousedown | mousedown | contextmenu |
UP | mouseup contextmenu |
mouseup | mouseup | contextmenu mouseup |
- | - | mouseup | mouseup |
There are no click events for right button clicks in any browser.
Opera normally generates no events for the right mouse button. For some time there has been an option that turns those events on and recently they have even started working correctly when it is enabled, but I believe that that option still defaults off.
Notice that the contextmenu event does not exist in all browsers, though some of the ones missing it still have a context menu that appears by default on right button clicks. It also occurs at different times in different browsers. I think this relates to the different behavior of the context menus in different browsers. In some browsers the context menu comes up when the mouse key is depressed, while in others it appears when it is realeased. This seems to change a lot between different browser versions. I think older versions of Safari had it fire on mouseup, but I don't know what versions. Probably it is risky to depend on any particular behavior from this event.
There is not a lot of consistency in the behavior of the WebKit browsers on this, though the only one that is distinctly bad is Epiphany, which fails to send a mousedown. The others vary only on the timing of the contextmenu event.
The question of what happens on middle clicks is less academic than it used to be. In the olden days, only Unix machines commonly had three button mice. But now most Windows mice have a scroll wheel between the two buttons that can be clicked and acts as a middle button. However, Firefox and Opera still have default actions associated with middle mouse buttons that cannot be disabled from Javascript, So only in Internet Explorer, Konqueror, and the WebKit browsers does the middle mouse button really work.
Internet Explorer WebKit |
Gecko Opera ≥ 8.0 Konqueror |
Opera < 8.0 | |
DOWN | mousedown | mousedown | mousedown |
UP | mouseup click |
mouseup | - |
Note that there is some disagreement about whether or not there should be a click event for middle buttons and older versions of Opera were broken, but otherwise things are pretty sensible.
IE ≥ 9.0 Gecko ≥ 1.7 Opera ≥ 9.10 WebKit ≥ 412 (Win & Mac) WebKit ≥ 533.4 (Linux) |
IE < 9.0 | Gecko 1.6 (Windows and Linux) Webkit ≤ 532.5 (Linux) |
Konqueror Webkit 312 (Mac) |
Opera ≥ 8.0 < 9.10 | Opera < 8.0 | |
DOWN | mousedown | mousedown | mousedown | mousedown | mousedown | mousedown |
UP | mouseup click |
mouseup click |
mouseup click |
mouseup click |
mouseup click |
mouseup click |
DOWN | mousedown | - | mousedown mousedown |
mousedown | dblclick click |
click dblclick |
UP | mouseup click dblclick |
mouseup dblclick |
mouseup click dblclick |
mouseup dblclick |
mouseup | mouseup |
Until recently, Internet Explorer has the annoying property of not giving a mousedown on the second click of a double click. In fact there was no event fired then at all.
Ancient versions of the Mozilla browsers made up for IE's lack of a mousedown event on the second click by sending two, so that overall, a double click sent three mousedown events. Macintosh versions of Gecko never had this problem.
Curiously, many years later, the first WebKit browsers for Linux, Chrome and Epiphany, revived the exact same double mousedown bug on double-clicks. This has been fixed in newer versions of Chrome, but likely not yet in Epiphany, since they are still using an older version of WebKit.
What older versions of Opera were up to is hard to imagine. That the first click event was on a mouseup, but the second was on mousedown was just the beginning of the madness.
Luckily, recent versions of the Mozilla browser and Opera have adopted sane behaviors, and the older versions are mostly out of circulation by now (Gecko 1.6 was last used in Firefox 0.8). The Safari developers wisely chose this as one of their rare instances of incompatibility with IE and chose to emulate Gecko instead. And now even Microsoft has belatedly gotten things right. Hurrah!
Right button double-click events are listed in the next table. Newer versions of Opera are not listed here, because it is no longer possible to execute a right button double-click in Opera. The first click cannot be stopped from activating the context menu, so the second click is caught by the context window, not the browser window.
Gecko ≥ 2.0 (Win) Internet Explorer Opera ≥ 10.50 |
2.0 > Gecko ≥ 1.7 (Win) Gecko ≥ 1.7 (Linux) Safari Chrome ≥ 1.0 (Win & Mac) Chrome ≥ 5.0 (Linux) |
Chrome 0.2 | Gecko 1.6 (Windows and Linux) Chrome 4.0 (Linux) |
Epiphany 2.28 | Gecko ≥ 1.7 (Mac) Konqueror |
|
DOWN | mousedown | mousedown contextmenu |
mousedown | mousedown contextmenu |
contextmenu | mousedown |
UP | mouseup contextmenu |
mouseup | contextmenu mouseup |
mouseup | mouseup | mouseup |
DOWN | mousedown | mousedown contextmenu |
mousedown | mousedown contextmenu mousedown contextmenu |
contextmenu contextmenu |
mousedown |
UP | mouseup contextmenu |
mouseup | contextmenu mouseup |
mouseup | mouseup | mouseup |
Happily, IE doesn't omit the second mousedown event in this case, but old versions of Gecko and new Linux WebKit browsers stutter on it just as they do on all the other buttons. Epiphany combines the stutter with missing mousedown events to achieve glorious new levels of brokeness.
If we ignore the stutters, then all browsers treat this simply as two clicks in a row, not a doubleclick.
Opera used to only trigger right click events if an obscure option was enabled by the user. In those versions, double right clicks were not possible because even if you enabled the option, context menu would always appear on the first click, so you couldn't perform a second one.
Finally, here are the events for double-clicks on the middle button, a button that isn't actually usable Gecko and Opera. Like the right click case, this is mostly just treated as two single clicks.
Internet Explorer | WebKit (Win & Mac) Webkit ≥ 433.4 (Linux) |
Gecko ≥ 1.7 Opera ≥ 8.0 Konqueror |
Gecko 1.6 (Windows and Linux) |
WebKit ≤ 532.5 (Linux) |
Opera < 8.0 | |
DOWN | mousedown | mousedown | mousedown | mousedown | mousedown | mousedown |
UP | mouseup click |
mouseup click |
mouseup | mouseup click |
mouseup | - |
DOWN | mousedown | mousedown | mousedown | mousedown mousedown |
mousedown mousedown |
mousedown |
UP | mouseup click |
mouseup click dblclick |
mouseup | mouseup | mouseup click dblclick |
- |
This is pretty much what you'd expect except that here, for once, the WebKit browsers do not perfectly emulate IE. Of course, it makes perfect sense that dblclick should be fired if click is, and firing an extra event is unlikely to cause compatibility problems, so this is probably a good choice.
It should be noted that double-clicks can be awkward to work with. If you want different things to happen on single-clicks and double-clicks then you will have to deal with the problem that both events will be firing when the user double-clicks. Your click handler is going to be called once or twice before your dblclick handler, and possibly once afterwards (on old versions of Opera). You are going to have a heck of a time suppressing the single-click action during a double-click. You can try various kludgy things with timers, but I don't think there is any reliable way to do this.
But if you are having this problem, then it is probably a case of bad interface design on your part. There is always a danger of what the user intends as a double-click being interpreted as two single-clicks. This is inherent in the idea of a double-click. So the single-click action shouldn't ever be something utterly different from the double-click action unless you really like annoying your users. An example of an appropriate use of double-click would be if a single-click selects a file icon, and a double-click opens it. In that case it doesn't matter of the file gets selected once or twice before being opened, because the double-click action is an extension of the single-click action. If your design doesn't fit into this pattern, then probably you should be use right-clicks or shift-clicks or clicks on different elements rather than double-clicks.
It used to be that every browser used a different combination of values to indicate the buttons, but there has been a gradual convergence on a standard now, so all browsers except IE work alike:
IE < 9.0 IE with attachEvent |
Netscape 4 | IE ≥ 9.0 Gecko ≥ 1.0 Webkit ≥ 523 Opera ≥ 8.0 Konqueror ≥ 4.3 |
Gecko 0.9 | Opera < 8.0 | Konqueror ≤ 3.5 | Webit 412 | ||
event.button | LEFT BUTTON |
1* | undefined | 0 | 1 | 1 | 1 | 1 |
MIDDLE BUTTON |
4* | undefined | 1 | 2 | 3 | 4 | ||
RIGHT BUTTON |
2* | undefined | 2 | 3 | 2 | 2 | ||
event.which | LEFT BUTTON |
undefined | 1 | 1 | 1 | 1 | 1 | 1 |
MIDDLE BUTTON |
undefined | 2 | 2 | 2 | 3 | 2 | ||
RIGHT BUTTON |
undefined | 3 | 3 | 3 | 2 | 3 |
Newer versions of all browsers have converged on column three of this table, but for older browsers this is a pretty serious mess.
The event.which value was originally used in Netscape, and the event.button value was originally used in Internet Explorer. Later browers used both, and messed them both up. Event.button was an especially hopeless mess, with different browsers returning 1, 2, 3 or 4 for a middle mouse click. Event.which is better, except that old versions of Opera got the middle and right mouse buttons mixed up, and, until recently, IE didn't return it at all.
It appears that in version 9.0, Microsoft did a lot of fixing here, so that usually it behaves just like all the other browsers. Microsoft seems to be keeping this a secret for now, as their documentation only describes the old behavior. And if you set your event handlers up using Microsoft's old, non-standard's compliant attachEvent() function, then you still get the old behavior in IE 9.0. If you set it up any other way, then you get the standards compliant behavior. So depending on how you wrote your workarounds for old IE bugs, you may or may not be seeing the fixed behavior of IE 9:
If you did:
if (elem.addEventListener) elem.addEventListener(eventname, func, true); else elem.attachEvent('on'+eventname, func);IE 9.0 obeys the standard! |
If you did:
if (elem.attachEvent) elem.addEventListener(eventname, func, true); else elem.attachEvent('on'+eventname, func);IE 9.0 acts like old IE |
Note that older versions of IE returned useful values in event.button only on mousedown and mouseup events. It was not possible to identify the mouse button for a click, dblclick or contextmenu event because event.button will be zero regardless of which button was pressed. This remains true of IE 9 when event handlers are set up with attachEvent().
A oddity in the old IE behavior became apparent if you held down more than one mouse button at a time. On mousedown events, IE returned the boolean-or of the values for all mouse buttons currently depressed. So if you held down both the left and right mouse buttons, the second mousedown event would have event.button=3. Note that in this case you cannot tell which of the two buttons triggered the current event. Your only hope is if you caught the previous mousedown event when the first of the two buttons were pressed and remembered which it was, and even that won't work if the user depressed one mouse button before moving it into your browser window. IE did not do this on mouseup events, and no other browser ever did this on any events. Again, you still see this with IE 9 if you set up your event handlers with attachEvent(), but not if you set them up any other way.
The DOM 3 Events Specification defines button identification attributes for mouse events. One is event.button which works the way most browsers now implement it. The other is event.buttons which is very similar to IE's old handling of event.button, with values 1, 2, 4 adding up if more than one button is held at the same time. Internet Explorer 9.0 is the first browser I've seen implement this, though only if you did not register the event handler with attachEvent().
Note that event.buttons is included on click, dblclick and contextmenu events, Also it's behavior on mouseup events differs from the old Microsoft event.button values. It always gives the button state after the event, so on a mouseup after an ordinary click of just one button, event.buttons would be zero, because no buttons are down.
Some mice now have more than three buttons. The DOM3 standard says these should be treated pretty much like the other buttons, assigning additional button numbers to them consecutively. I have done only a little testing of this. Most browsers now seem to take the fourth and fifth buttons as "BACK" and "FORWARD" commands, but generate no Javascript events. The older versions of Konqueror I tested generated events with values of 65536 for button and which, which is probably intended as an error code, not a key identifier.
The end result of all this? While there is no browser-independent way to recognize which mouse button is which for all the browsers surveyed, you can come extremely close. The following test works for all browsers except Opera 7. (It mixes up the right and middle mouse buttons in Opera 7, but Opera 7 is pretty old, there were only a few versions where the right and middle buttons even worked, and then only if the user set an obscure option, so you really don't care.) It also doesn't correctly handle simultaneous clicks of multiple mouse buttons in IE. To do that your application needs to track which buttons were reported to be down in previous mousedown events so it can figure out which one was added.
if (event.which == null) /* IE case */ button= (event.button < 2) ? "LEFT" : ((event.button == 4) ? "MIDDLE" : "RIGHT"); else /* All others */ button= (event.which < 2) ? "LEFT" : ((event.which == 2) ? "MIDDLE" : "RIGHT");
If you don't have any other compelling reason to prefer one event over another, it's probably generally best to attach your event handlers to mouseup. IE versions before 9.0 had annoying little problems with the others (click doesn't identify the mouse button, and mousedown ORs together mouse button values and isn't fired on the second click of a double-click) and WebKit versions of Epiphany fail to send mousedown. Triggering on mouseup also means that users get a last chance to change their mind about clicking on something - they can move their mouse off the control before releasing the button.
For all modern browsers, all mouse events include the booleans event.shiftKey, event.ctrlKey, and event.altKey. These are true if that key was down during the mouse event.
I haven't been tracking these as carefully as other aspects of mouse events, but I think the last browser that didn't support these was Netscape 4, which used the e.modifiers field instead. To check if the shift key was down, you check if (e.modifiers & Event.SHIFT_MASK) != 0 was true. For the other keys you would use Event.CONTROL_MASK or Event.ALT_MASK in the same way. I think you can safely file this under the category of "Irrelevant Historical Trivial" though.
In all browsers these are in event.screenX and event.screenY.
In all browsers these are in event.clientX and event.clientY.
In most browsers these coordinates are returned in event.pageX and event.pageY. But IE does not implement these. For IE, you need to compute them by adding the current scrolling offset to the the window coordinates. Unfortunately, the scrolling offset is stored in different places in different versions of IE, so this gets a bit complex. The following works in all browsers:
if (event.pageX == null) { // IE case var d= (document.documentElement && document.documentElement.scrollLeft != null) ? document.documentElement : document.body; docX= event.clientX + d.scrollLeft; docY= event.clientY + d.scrollTop; } else { // all other browsers docX= event.pageX; docY= event.pageY; }A demo of this code is available.
Theoretically, the event.offsetX and event.offsetY values are supposed to contain this, but forget it. There are so many bugs and incompatibilities in these values that they are essentially completely useless.
Instead you'll need to subtract the document coordinates of the target element from the document coordinates of the mouse click. (If you are computing the coordinates of the target element, remember that offsetLeft and offsetTop are relative to offsetParent which is not necessarily the document. You may need to climb the chain of offsetParents adding up offsets as you go.)
There are some other mouse events. The mouseover and mouseout events are triggered whenever the mouse moves on to or off of an object. The mousemove event is triggered whenever the mouse is moved. These events are handled pretty consistently in different browsers so far as I have been able to tell, but there are some oddities.
A test script for the mouseover and mouseout events is available at http://unixpapa.com/js/testover.html. The test script shows two nested <DIV> blocks, with some text inside the inner one. It's important to note that in all browsers, when the mouse moves from the outer block into the inner block, there is a mouseout event fired for the outer block. The concept is that the mouse is never "in" more than one object at a time, and the one it is "in" is the frontmost of those in whose enclosing box it falls.
Internet Explorer has some additional events, mouseenter and mouseleave which fire only when you cross the outer boundaries of objects. These are more convenient for many applications, however no other browser supports these events. They might do so in the future, as the DOM 3 standard mandates them.
Safari 3 and other modern Webkit browsers seem to handle mouseover and mouseout correctly, but older versions of Safari had real problems. They also generated mouseover and mouseout events for text nodes. So, on the test page, when you moved the mouse over the text inside the inner <DIV> you would get a mouseout event on the <DIV> and then a mouseover event on the text. I don't think other browsers can even fire events on text nodes.
In Safari 1.3, the mouseover event fires when the mouse pointer enters the rectangular box surrounding the text. This was strange but understandable. In Safari 2.0, when you swept the mouse pointer over some text in a <DIV> you would get dozens and dozens of mouseover and mouseout events. At first I thought they were triggering events on the actual boundaries of the letters, but that didn't seem to be the case. Then I noticed that on the text you get only mouseout events, and on the <DIV> you only get mouseover events, so basically the firing of these events as you go over text was just completely wacky.
In applications it's usually the spurious mouseout events on the <DIV> that cause problems. In Safari 2.0, you only get spurious mouseover events, which are usually less of a problem. The best general solution I've found for Safari 1.3 is to have the event handler on the <DIV> check if the mouse is still actually within the bounds of the <DIV> and ignore the event if it is. Since finding mouse coordinates and block borders is pretty browser dependent too, and takes a bit of computation, I tend to perform that test only on Safari.
However, even if you don't want to drop and drag anything, it is worth knowing a bit about these things, because they can interfere with ordinary mousing around. Sometimes the browser will decide that the user is trying to do a drag, and everything will go wrong. Events like mousemove and mouseover stop firing when the browser thinks a drag is in progress.
In most browsers, just disabling the browser default action on the mousedown event (see above for how to do this). seems to disable this. I'm pretty sure that in older versions of gecko it was necessary to disable the default action on the dragstart event, but this no longer seems necessary.
There are also events fired in some browsers when text is selected with the mouse. These also may be undesirable if you are trying to have something else happen with mouse clicks. Disabling the default action on mousedown for the page body seems to solve this for most modern browsers, but this didn't always work on older browers. I've heard that canceling the browser default on the selectstart event helped with this on some browsers.