Javascript Madness Intro

Javascript Madness: Mouse Events

Jan Wolter
Aug 12, 2011

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.

Introduction

This document summarizes the results of some browser tests done during the development of Web Paint-by-Number, a Javascript application that makes intense use of mouse events. It documents inconsistancies in the way different browsers implement mouse events.

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.

Mouse Buttons and Default Events

When you click on something displayed by your browser, the browser generates some mouse events. Normally these events trigger some default action by the browser, like following a link or bringing up a context menu. But it is possible to set up Javascript event handler functions that called get instead. These can perform some special action, and then they can either cancel the default action, or allow it to go forward.

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:

So all is well now with right clicks in Opera—unless mouse gestures are turned on. This causes all sorts of side effects when the mouse is moved with the right button depressed, none of them cancelable from Javascript. Oh well.

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:

Can default mouse actions be disabled?
Left ButtonMiddle ButtonRight Button
Internet Explorer
WebKit
Konqueror
YesYesYes
Gecko YesNoYes
Opera ≥ 10.50 Yes No Yes
Opera < 7.23
Opera 8.5 - 9.63
YesNoNo
Opera 7.23 - 7.54 Yes No Only if user has enabled
an obscure option

Types of Mouse Events

The events that are associated with mouse clicks are:

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.

Event Triggering - Single Clicks

If you do a single mouse click of the left mouse botton, every modern browser I've checked does exactly the same thing. When the mouse goes down, a mousedown event is triggered. When the mouse goes back up, first a mouseup event and then a click event is triggered. Very nice.

Single Click of Left Mouse Button
All Browsers
DOWNmousedown
UPmouseup
click

So, here's what happens when you do click on the right mouse button:

Single Click of 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.

Single Click of Middle Mouse Button
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.

Event Triggering - Double-Clicks

Now let's turn our attention to double-clicks. Even if you just want to treat double-clicks as two consecutive clicks, you need to pay attention to the weirdnesses here, because strange things happened in many older browsers when two clicks happen to be close enough together to register as a double-click. Here's what we see with a double-click on the left mouse button:

Double-Click of Left Mouse Button
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.

Double-Click of Right Mouse Button
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.

Double-Click of Middle Mouse Button
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.

Identifying Mouse Buttons

When Javascript recieves a mouse click event, it is generally interesting to know which mouse button was clicked. The event object that is passed to Javascript contains two fields that may be used to check this, event.which and event.button. These will contain numerical values to identify the mouse button.

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:

Mouse button ID values in various browser
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  
* for mousedown and mouseup only, zero for click, dblclick and contextmenu events.

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");
Click here with various mouse buttons to test

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.

Detecting Modifier Keys

Being able to attach different actions to different mouse buttons is great, except for your users with one-button mice. On those the convention is to use shift-clicks or control-clicks instead, clicking the mouse button while holding down a keyboard modifier. One might assume that you'd need to track both keyboard events and mouse events to be able to recognize this, but it turns out that you're in luck, mouse events include information about the current status of the keyboard modifier keys.

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.

Finding Mouse Positions

So, you want to know the coordinates where a mouse click occurred? The mouse event object does include coordinates. In fact there may be as many as six different sets of coordinates in some browsers, and, naturally, they are a swamp of browser incompatibilities. Luckily, I don't have to document them in detail, because Quirksmode already has. I will however give you the short answer, which depends on which coordinates you want:

Mouseover and Mouseout

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.

Drag and Text Selection Events

Some browsers have special support for dragging and dropping HTML options using the mouse. I've done minimal study of these. They seem to be targetted primarily toward dragging object between browser windows, possibly from one application to an unrelated one. For simply dragging things around within your application, they seem rather clumsy. Reading the Mozilla drag and drop documentation might be a start for those interested in this.

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.


Last Update: Thu Aug 30 11:49:11 EDT 2012