近日作需求,發現一個經常使用插件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
咱們打印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目前已經變成最大的web標準,文檔集散地了