dwi2的碎念

不能只是曬網

JavaScript Wheel Event - 使用JavaScript處理瀏覽器的滑鼠滾輪事件

| Comments

之前跟大家介紹過了如何用JavaScript處理混亂的Keyboard Event (其1, 其2). 今天要跟大家介紹瀏覽器的滾輪事件wheel event. 滾輪事件不只是滑鼠滾輪會觸發, Macbook的觸控板上雙指滑動其實也是滾輪事件.

實際開發web application需要偵測wheel event的需求或情境也許不多, 網路上能找到的介紹也不多, 但各個瀏覽器對滾輪事件的實作卻是十分混亂, 處理滾輪事件一點都不輕鬆!

一般來說, 滾輪事件發生時, 我們關心的資料是

  1. 滾輪滾動事件發生與否
  2. 滾輪滾動的距離

大多數瀏覽器都有提供wheel event, wheel event內都有一個類似delta的屬性表示滾輪移動的距離, 不過delta的正負值在不同瀏覽器裡面代表的意義則有些許差異, 此外屬性名稱也不一定叫delta, 事件名稱也不一定叫wheel!

註冊滾輪事件及滾輪事件重要屬性

Internet Explorer

先從眾多前端設計師和程式設計師痛恨的Internet Explorer看起. IE 8和以前版本提供了非標準的mousewheel事件, 但從IE 9以後的版本就多增加了WheelEvent事件. WheelEvent是DOM level 3提出的標準滾輪事件. 在IE 8及之前版本的滾輪事件雖然叫mousewheel, 但註冊的時候要使用的事件名稱卻要用onmousewheel:

1
element.attachEvent('onmousewheel', event_handler);

mousewheel事件發生時, event內有個wheelDelta屬性, 即是滾輪的滾動值(整數).

IE 9以後的版本提供兩種滑鼠滾輪事件: WheelEvent和MouseWheelEvent. 其中MouseWheelEvent比較接近IE 8以前的滑鼠滾輪事件, WheelEvent則是符合DOM Level 3規範的滾輪事件. IE 9註冊WheelEvent的方式大抵如下:

1
element.addEventListener('wheel', event_handler);

滾動值(一般是垂直方向)則是存在deltaY屬性內. 至於舊的MouseWheelEvent, 我的建議是不要使用, 畢竟它不是標準. 另外要注意的地方, mousewheel或是MouseWheelEvent的wheelDelta正值表示滾輪向上, 負值表示向下; 但新的WheelEvent的deltaY正值表示滾輪向下, 負值代表向上, 剛好相反!

Firefox

Firefox在滾輪事件上也是改版改很大, 在Firefox 16以前, 有兩種事件可供使用, 分別是DOMMouseScroll以及MozMousePixelScroll. 但Firefox 17及以後版本, 新增了DOM Level 3版本的WheelEvent. Firefox 16以前的滾輪事件註冊方式:

1
element.addEventListener('DOMMouseScroll', event_handler);

或是

1
element.addEventListener('MozMousePixelScroll', event_handler);

DOMMouseScroll和MozMousePixelScroll的事件發生時, 這兩種事件內都有個detail屬性, 代表滾輪滾動的數值. 但DOMMouseScroll和MozMousePixelScroll裡面的detail值數值意義稍微不太一樣, 對於相同滾動距離的滾輪事件, MozMousePixelScroll的detail會比DOMMouseScroll的detail大, 因為MozMousePixelScroll代表的是滾動的pixel距離. 此外如果DOMMouseScroll和MozMousePixelScroll兩種事件都有註冊的時候, DOMMouseScroll會先被觸發.

在Firefox 17以後的版本則是提供標準的DOM Level 3 WheelEvent(如同IE 9):

1
element.addEventListener('wheel', event_handler);

事件發生時, 滾動值(垂直方向)也是存在deltaY屬性內.

無論是DOMMouseScroll, MozMousePixelScroll還是WheelEvent, detail或deltaY的正值都是表示滾輪向下, 正值表示向上.

Chrome 和 Safari

Chrome和Safari狀況類似, 因此擺在一起說明. Chrome和Safari提供的滾輪事件雖然都叫WheelEvent, 但實作上卻比較接近非標準的IE 8的mousewheel, 註冊事件用的名字也是mousewheel, 註冊方式如下:

1
element.addEventListener('mousewheel', event_handler);

滾輪事件內部都提供wheelDelta(或是wheelDeltaY也可以得到同樣數值)屬性代表滾輪滾動距離, wheelDelta正值代表滾輪向上, 負值表示向下.

重點整理

從上面的段落看下來可以發現, 各瀏覽器對滾輪事件處理狀況組合非常多種, 因此我將各瀏覽器的主要滾輪事件以及滾輪滾動值屬性名稱整理如下表:

偵測滾輪事件

已經知道滾輪事件很混亂了, 接下來的問題是, 什麼時候要註冊哪一種? 要如何偵測滾輪事件? 那就來看下面這段code

偵測滑鼠滾輪事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// detect event model
if (window.addEventListener) { // for modern browser
  _addEventListener = "addEventListener";
} else { // for IE 8 or older
  _addEventListener = "attachEvent";
}

// detect available wheel event
if (document.onmousewheel === null
    && window.addEventListener === undefined) {
  // IE 8 or older
  wheel_event_name = 'onmousewheel';
} else if (Modernizr.hasEvent('wheel') || (!!window.WheelEvent && !!window.MouseWheelEvent)) {
  // detect if browser has DOM L3 wheel event: Firefox 17 and IE 9 or later version
  wheel_event_name = 'wheel';
  inverse_wheel_direction = true;
} else if (!!window.WheelEvent) {
  // Safari, Chrome
  wheel_event_name = 'mousewheel';
} else {
  // Firefox 16 and earlier version
  wheel_event_name = 'DOMMouseScroll';
  inverse_wheel_direction = true;
}
document[_addEventListener](wheel_event_name, wheelHandler);

這裡會利用一個JavaScript library叫Modernizr, Modernizr的主要功能就是幫助開發人員偵測HTML5/CSS3 feature, 它有提供一個hasEvent()的函式, 可以幫忙判斷瀏覽器是否有提供某類事件偵側支援, 用法可以參考此處

判斷邏輯是:

  1. 先對付老舊瀏覽器如IE 8, 看看可否addEventListener, 還有document物件下是否有onmousewheel屬性, 要注意的是這裡是偵測document.onmousewheel === null, 這個寫法不同於document.onmousewheel == null, 因為我們必須精確判斷document下面是否有已存在尚未註冊(null)的onmousewheel, 如果只有兩個等號, 會把undefined的狀況也納進來, 那就無法正確偵測了. 這個case就註冊’onmousewheel’來處理滾輪事件.

  2. 利用Modernizr來幫我們測試是否有wheel (WheelEvent)存在, 有的話就註冊’wheel’以處理滾輪事件. 不過Modernizr無法偵測到IE 9是否有wheel事件, 因此額外加入(or condition)一個條件就是看window.WheelEvent和window.MouseWheelEvent是否存在. 有些人可能會疑惑為何要多看一個window.MouseWheelEvent呢? 答案其實要往下看code才知道. Safari和Chrome的window object內也有WheelEvent, 因此如果只判斷window內是否有WheelEvent可能會誤判為Chrome和Safari, 所以最好是檢查window內是否同時有WheeEvent和MouseWheelEvent. 這種時候要註冊標準的DOM Level3 WheelEvent ‘wheel’來處理滾輪事件.

  3. 這時剩下的瀏覽器只有Safari, Chrome和Firefox 16以前版本, 因此這時就看window是否有WheelEvent, 有的話就是Safari和Chrome, 此時要註冊’mousewheel’以處理滾輪事件. 有些人可能會疑惑為何這裡不用Modernizr判斷? 例如用Modernizr.hasEvent(‘mousewheel’)來判斷. 然而實際上如果用mousewheel判斷的話, IE 9, Chrome, Safari三者都會回傳true, 但在IE 9我們有更接近標準的WheelEvent ‘wheel’ 可以用, 此時應該把IE 9和Safari/Chrome分開, 讓IE 9使用標準的DOM Level 3 WheelEvent.

  4. 最後剩下的可以合理推斷應該是Firefox 16以前版本, 那就用註冊’DOMMouseScroll’來處理滾輪事件.

在事件處理函式中, 我們可以用下面的方式取得滾輪滾動數值

1
var delta = evt.deltaY || evt.wheelDelta || evt.detail;

不過這個delta值的正負所代表的意義也會因為瀏覽器不同而有所不同, 因此在稍早偵測滾輪事件種類時, 我用一個變數inverse_wheel_direction幫助判斷. 當inverse_wheel_direction為true時, delta值的負值表示滾輪向上, 反之表示向下.

這段code的完整版以及圖片整理在 http://dwi2.com/feature_detection/, 畫面中藍色區塊會表示當前瀏覽器偵測到的滾輪事件是哪一種, 當你在網頁中滾動滑鼠滾輪時, 綠色區塊會即時顯示目前觸發的滾輪事件以及delta值

總結

結論就是要處理瀏覽器的滑鼠滾輪事件, 看這張圖還有這段code就夠了! XDD

資料來源

MDN DOMMouseScroll

MDN MozMousePixelScroll

MDN wheel

MSDN onmousewheel

MSDN WheelEvent

Comments