Mouse wheel support is one of those things that have long relied on proprietary non-standard events. Now that I’ve been having a (quite passive) look around for new wheel supporting snippets of code around the web in places like CodePen or some dev blogs I’ve noticed that a) a lot of people dev on Chrome, and b) if Chrome doesn’t use the standard way, many don’t bother to even lookup the standard method and instead rely only on non-standard stuff, assuming what they’ve used is the standard way.
Mouse wheel stuff can be quite deceiving. There is the Internet Explorer 6 originated mousewheel event which is supported by everything except Firefox / Gecko. The problem with this event is it’s name: by it it seems like it would be a standard event. And this is not correct, which is also why I think Firefox got this right by not supporting it and by using a clearly non-standard name instead: DomMouseScroll.
But these days we also do have a standard event. The name of it? The wheel event. And it is supported since Firefox 17 and Internet Explorer 9. The good thing about this new event is that if we don’t need to support Firefox 3.6 (which is the last version that didn’t have the automatic upgrade feature) then we really only need to support two events: wheel and mousewheel.
Implementing a modern cross-browser solution
If you’re using jQuery and don’t mind plugins then there is already a quite excellent normalizing mousewheel plugin. Just bind yourself to one event and you get (nearly) the same results in a wide range of browsers and platforms. Just for a note though I don’t fully agree with all of the choices in the plugin’s internal logic: if wheel event is implemented in Webkit then (if I’ve read correctly) the old proprietary methods are currently preferred over the standard event. This is minor, but I think one should always prefer the standard way unless there is a bug in implementation.
However there are people who like to do things without using a library or a framework. So here I go through two implementations and also add notes about some gotchas.
Minimal implementation with DOM Level 1 events
If you are the only person that ever touches the code and you don’t need to care about constant development or future changes by others, which is the case for most personal sites, then I think there is nothing wrong in using the old event model. It is simple to use, every single browser supports it 100% and it works fine if you don’t want to go around and apply multiple wheel event handlers to the same element. And most often we really don’t need to.
Here are a few pointers for the simplest implementation:
- Use onwheel as the main handler. Firefox is the only browser that actually does this right now.
- We cancel bubbling in onwheel event so that the other wheel events are not triggered.
- All other browsers rely to the onmousewheel, even IE9 and IE10 (they only support wheel event via addEventListener).
- We make event object in onmousewheel event compatible with onwheel event and then call the latter.
- To ensure more useful delta data we normalize the value.
With all of this the resulting skeleton code is simple and short:
And if you like eye candy then this is more pleasant to look at:
It uses the same core idea, just has more features around it for cross-browser, cross-device usability.
Implementation with addEventListener
addEventListener is the preferred way to handle the event. However there is one notable issue that forces us to consider attachEvent: Internet Explorer 8 always works as if preventDefault() is called if mousewheel event is listened using addEventListener. There is no way around this: we have to sniff for IE8 or default to attachEvent first. As much as I dislike it I’ve chosen to sniff, because we can detect IE8 reliably and the bug is only in IE8. If you always want preventDefault() or if you don’t need to support IE8 then you can throw IE8 stuff away.
We do not add support for Firefox 16 or older here. Adding extra handler for it isn’t too hard, just copy mousewheelCompatibility and implement DOMMouseScrollCompatibility and add a listener to it. Also you have to use e.detail instead of e.wheelDelta.
And there you have it: mouse wheel solution that prefers a fresh standardized wheel event with a fallback to mousewheel event if it isn’t supported.