tinyscrollbar鎖滾動問題引出對wheel事件的探索

tinyscrollbar鎖滾動問題

近日作需求,發現一個經常使用插件jquery.tinyscrollbar忽然出毛病了,因而探究了一番箇中緣由。css

出問題的場景

在ie,chrome以及其餘主流瀏覽器下,jquery.tinyscrollbar可以正常響應並滾動自定義滾動條。但在最新版firefox下,準確的說是OS X系統環境下,發生了自定義滾動條鎖滾動的問題。jquery

尋找問題源頭

咱們發現場景下,是滾動了一段距離後,插件忽然卡住。linux

因而我開始翻閱jquery.tinyscrollbar的源碼,尋找設置內容位置的代碼。jquery.tinyscrollbar是經過監聽頁面滾輪事件,進一步進行設置內容位置來實現自定義滾動條的。其中響應滾動事件的方法是_wheel,那好,咱們來看_wheel的源碼git

經過閱讀jquery.tinyscrollbar源碼發現,有一段源碼是如此陳述的:web

/**
 * @method _wheel
 * @private
 */
function _wheel(event) {
    if(self.hasContentToSroll) {
        // Trying to make sense of all the different wheel event implementations..
        // 接洽原生事件
        var evntObj = event || window.event
        // 獲取滾輪的差量
        ,   wheelDelta = -(evntObj.deltaY || evntObj.detail || (-1 / 3 * evntObj.wheelDelta)) / 40
        // 獲取滾動單位
        ,   multiply = (evntObj.deltaMode === 1) ? self.options.wheelSpeed : 1
        ;
        // 計算內容位置
        self.contentPosition -= wheelDelta * multiply * self.options.wheelSpeed;
        self.contentPosition = Math.min((self.contentSize - self.viewportSize), Math.max(0, self.contentPosition));
        // 計算滾動條位置
        self.thumbPosition = self.contentPosition / self.trackRatio;

        /**
         * The move event will trigger when the carousel slides to a new slide.
         * 向外拋出移動事件
         * @event move
         */
        $container.trigger("move");

        // 設置滾動條位置
        $thumb.css(posiLabel, self.thumbPosition);
        // 設置內容位置
        $overview.css(posiLabel, -self.contentPosition);

        // 判斷是否觸底,觸底的話阻止事件繼續觸發
        if(self.options.wheelLock || _isAtBegin() && _isAtEnd()) {
            evntObj = $.event.fix(evntObj);
            evntObj.preventDefault();
        }

        if(_isAtBegin() && _isAtEnd()){
            evntObj.stopPropagation(); 
        }
        
    }
}

既然是內容滾不動了,那麼問題必定出在設置內容位置的地方了chrome

肯定問題

咱們在這個方法裏對self.contentPosition打了個log,果不其然,self.contentPosition顯示爲NaN。要知道NaN和任何數作計算最後所得的值都是NaN的,因此出現了bug。瀏覽器

那麼這個NaN是哪裏來的呢?繼續對wheelDelta和multiply打log,發現wheelDelta產生了NaN值。dom

此時一頭黑人問號。近一步翻閱MDN發現,大部分現代瀏覽器支持wheel事件(DOM Level 2的事件),ie和webkit瀏覽器支持mousewheel事件(非標準),老的火狐瀏覽器支持的是DOMMouseScroll事件(非標準)。因而咱們開始探究幾個事件中的差別async

wheel事件所具備的read-only屬性ide

WheelEvent.deltaX
雙精度值,表示滾輪在水平方向的差量
WheelEvent.deltaY
雙精度值,表示滾輪在豎直方向的差量
WheelEvent.deltaZ
雙精度值,表示滾輪在z軸上的差量
WheelEvent.deltaMode
無符號長整形,表示滾動的單位, 0爲表示px,1爲表示以行爲單位滾動,2爲以頁爲單位滾動

wheel事件同時繼承了父類MouseEvent, UIEvent 和 Event的屬性

UIEvent.detail
返回一個長整型數值,用於描述該事件,依賴於事件類型

delta的值並不表明真實的滾動量,只是針對鼠標指針的位置來計算的


MouseWheelEvent read-only屬性

wheelDelta
長整型,以像素來計算的滾輪偏移值
wheelDeltaX
長整型,以像素來計算的滾輪x軸偏移值
wheelDeltaY
長整型,以像素來計算的滾輪y軸偏移值
detail
該值老是0,除了Opera瀏覽器之外

DOMMouseScroll只有一個屬性

detail
用於精確描述滾動,正值表示向下滾,負值表示向上滾。向上滾一頁-32768,向下滾一頁32768,其餘值表示滾動行數

重要描述,該值永遠不爲0

detail值的描述

在MouseWheelEvent中,

Opera設置這個值和firefox的Gecko內核DOMMouseScroll 事件的 detail是同樣的。都用來表示以行來滾動,負值用來表示向底部或者向右滾動。在mac上,滾動加速階段會計算該值。在linux上,是由原生滾動事件設置的,值爲2或者-2

因此新版firefox中wheel事件發生了啥?

咱們打印console.log(evt.deltaX,event.detail,event.wheelDelta)。發如今觸底的時候,日誌顯示(0,0,undefined),真相大白!

在webkit或者blink中,deltaX這個值永遠不會被設置爲0,而在新版firefox中(mac os x),這個值在某時阻尼滾動結束的時候是會變爲-0。因爲下式的斷言,最終整個計算中被引入了一個NaN

wheelDelta = -(evntObj.deltaY || evntObj.detail || (-1 / 3 * evntObj.wheelDelta)) / 40

是插件的缺陷嗎

很明顯,插件做者最初是沒有設想到deltaY爲0的狀況,因而依託或運算的截斷操做寫了這句經驗而來的代碼。對咱們這些工做者而言,這樣的判斷是不嚴謹的。若是嚴格按照瀏覽器的區分來判斷,就能避免這個問題。以測試而言,明顯做者沒有考慮到undefined這樣的極端狀況。

做者去哪兒啦?

在git上尋找做者,發現該倉庫已經年久失修了。。。做者棄坑而去

解決辦法

臨時的:
本身把插件源碼改了,將默認值設爲0,避免NaN引入的風險wheelDelta = -(evntObj.deltaY || evntObj.detail || (-1 / 3 * evntObj.wheelDelta)) / 40 || 0
永久的:
向火狐提issue~
可能實現的(極小):
向做者提pr

給咱們的啓示

一切按標準或者文檔來(MSDN有時候不可信,佐證以下

Interface :MouseWheelEvent
Synchronicity :asynchronous
Bubbles : yes (Though, MSDN documents "No")
Target :Element, Document, Window
Cancelable : yes (Though, MSDN documents "No")
Default action : Scroll, moving history, or zooming in/out

MDN賽高!

MDN目前已經變成最大的web標準,文檔集散地了

參考資料

MouseWheelEvent
DomMouseScrollEvent
WheelEvent

相關文章
相關標籤/搜索