1

JS滚轮事件(mousewheel/DOMMouseScroll)了解

 2 years ago
source link: https://www.zhangxinxu.com/wordpress/2013/04/js-mousewheel-dommousescroll-event/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

by zhangxinxu from http://www.zhangxinxu.com
本文地址:http://www.zhangxinxu.com/wordpress/?p=3175

一、学无止境、温故知新

//zxx: 本段与技术无关,一些很个人的吐槽,可以跳过
正常状态已经没有了小学生时代过目不忘的记忆力了,很多自己折腾的东西、接触的东西,短短1年之后就全然不记得了。比方说,完全记不得获取元素与页面距离的方法(getBoundingClientRect),或者是不记得现代浏览器下触发DOM自定义事件的方法(dispatchEvent). 显然,适当的温习,翻阅以前的东西,或者自己空余时间处理相关的东西还是有必要的。其实,细想,东西记不住是自己自身原因,在折腾的时候就没有想方设法牢记(而不是通过反复使用记住)。比方说getBoundingClientRect就是“得到客户端矩形边界”的意思,或者使用邪恶记法记住“割(g)逼(b)艹(c)软(r)”。dispatchEvent方法使用“3步走”,“创建(createEvent)-初始(init*Event)-分派(dispatchEvent)”。

学习的脚步不能停止。一站到底的那些“变态”们也有不知道的东西,显然,我们这些草辈,尤其年轻的自己,不知道的更多。谁年轻的时候没有过或多或少的迷茫,问自己“路在何方”,问自己“该做哪个方向”,无论你选择的是什么,学习的脚步是不能停止的。坚持着坚持着,路自然就会清晰,你就会知道接下来该怎么走了。只怕畏首畏尾,得过且过,年轻就是资本,义无反顾前行吧。

我凭着兴趣走上现在的道路,完全是兴趣学习(我喜欢这些,我要学),不是职业学习(做前端需要什么,我就去学什么)。工作的这些年,技术、产品的自我沉浸不知不觉限制了自己的眼界,好在意识到问题的存在其实已经解决了问题的一半。这里之所以会说这些是想提醒自己,万万不可矫枉过正,技术、产品的学习还是主要的,只是要多多抬头看看办公室之外的世界(不是刷微博获得的浅认识)。

昨天机缘巧合遇到“滚轮事件”,以前折腾“自定义滚动条”时候使用过鼠标滚轮事件,不过这是基于MooTools已经兼容好的mousewheel事件实现的,如果要说出其中的实现机制,浏览器兼容差异等,就傻眼了。学无止境,因此,查阅之,实践之,整理之。

二、兼容差异大全

滚轮事件的兼容性差异有些不拘一格,不是以往的IE8-派和其他派,而是FireFox派和其他派。

包括IE6在内的浏览器是使用onmousewheel,而FireFox浏览器一个人使用DOMMouseScroll. 经自己测试,即使现在FireFox 19下,也是不识onmousewheel

一个最简单的使用差异(body滚动条由内部一定高div撑开):

document.body.onmousewheel = function(event) {
    event = event || window.event;
    console.dir(event);	
};
document.body.addEventListener("DOMMouseScroll", function(event) {
    console.dir(event);	
});

以上输出差异见下面(IE7, IE10, Chrome, 以及FireFox,鼠标向下滚动, win7)(可点击此页面单独查看表格内容):

属性名\浏览器 FireFox Chrome IE10 IE7 recordset ×没有该属性 ×没有该属性 ×没有该属性 null type DOMMouseScroll mousewheel mousewheel mousewheel fromElement ×没有该属性 null null null toElement ×没有该属性 [object HTMLDivElement] null null altLeft ×没有该属性 ×没有该属性 ×没有该属性 false keyCode ×没有该属性 0 ×没有该属性 0 repeat ×没有该属性 ×没有该属性 ×没有该属性 false reason ×没有该属性 ×没有该属性 ×没有该属性 0 data ×没有该属性 ×没有该属性 ×没有该属性 空字符串 behaviorCookie ×没有该属性 ×没有该属性 ×没有该属性 0 source ×没有该属性 ×没有该属性 ×没有该属性 null contentOverflow ×没有该属性 ×没有该属性 ×没有该属性 false behaviorPart ×没有该属性 ×没有该属性 ×没有该属性 0 url ×没有该属性 ×没有该属性 ×没有该属性 空字符串 dataTransfer ×没有该属性 null ×没有该属性 null ctrlKey false false false false shiftLeft ×没有该属性 ×没有该属性 ×没有该属性 false dataFld ×没有该属性 ×没有该属性 ×没有该属性 空字符串 returnValue ×没有该属性 true ×没有该属性 undefined qualifier ×没有该属性 ×没有该属性 ×没有该属性 空字符串 wheelDelta ×没有该属性 -120 -120 -120 bookmarks ×没有该属性 ×没有该属性 ×没有该属性 null actionURL ×没有该属性 ×没有该属性 ×没有该属性 空字符串 button 0 0 0 0 srcFilter ×没有该属性 ×没有该属性 ×没有该属性 null nextPage ×没有该属性 ×没有该属性 ×没有该属性 空字符串 cancelBubble false false false false x ×没有该属性 799 876 839 y ×没有该属性 283 322 325 buttonID ×没有该属性 ×没有该属性 ×没有该属性 0 srcElement ×没有该属性 [object HTMLDivElement] [object HTMLDivElement] [object] screenX 934 799 876 841 screenY 453 344 377 382 srcUrn ×没有该属性 ×没有该属性 ×没有该属性 空字符串 origin ×没有该属性 ×没有该属性 ×没有该属性 空字符串 boundElements ×没有该属性 ×没有该属性 ×没有该属性 [object] clientX 1168 799 876 841 clientY 456 283 322 327 propertyName ×没有该属性 ×没有该属性 ×没有该属性 空字符串 shiftKey false false false false ctrlLeft ×没有该属性 ×没有该属性 ×没有该属性 false offsetX ×没有该属性 791 868 829 offsetY ×没有该属性 275 314 310 altKey false false false false initMouseWheelEvent ×没有该属性 ×没有该属性 function initMouseWheelEvent() { [native code] } ×没有该属性 layerX 1168 799 876 ×没有该属性 layerY 456 283 322 ×没有该属性 which 1 1 1 ×没有该属性 buttons 0 ×没有该属性 0 ×没有该属性 metaKey false false false ×没有该属性 pageX 1168 799 876 ×没有该属性 pageY 456 283 322 ×没有该属性 relatedTarget null null null ×没有该属性 getModifierState function getModifierState() { [native code] } ×没有该属性 function getModifierState() { [native code] } ×没有该属性 initMouseEvent function initMouseEvent() { [native code] } function initMouseEvent() { [native code] } function initMouseEvent() { [native code] } ×没有该属性 detail 3 0 0 ×没有该属性 view [object Window] [object Window] [object Window] ×没有该属性 initUIEvent function initUIEvent() { [native code] } function initUIEvent() { [native code] } function initUIEvent() { [native code] } ×没有该属性 bubbles true true true ×没有该属性 cancelable true true true ×没有该属性 currentTarget [object HTMLBodyElement] [object HTMLBodyElement] [object HTMLBodyElement] ×没有该属性 defaultPrevented false false false ×没有该属性 eventPhase 3 3 3 ×没有该属性 isTrusted true ×没有该属性 true ×没有该属性 target [object HTMLDivElement] [object HTMLDivElement] [object HTMLDivElement] ×没有该属性 timeStamp 14296937 1366106275177 1366106216522 ×没有该属性 initEvent function initEvent() { [native code] } function initEvent() { [native code] } function initEvent() { [native code] } ×没有该属性 preventDefault function preventDefault() { [native code] } function preventDefault() { [native code] } function preventDefault() { [native code] } ×没有该属性 stopImmediate
Propagation function stopImmediate
Propagation() { [native code] } function stopImmediate
Propagation() { [native code] } function stopImmediate
Propagation() { [native code] } ×没有该属性 stopPropagation function stopPropagation() { [native code] } function stopPropagation() { [native code] } function stopPropagation() { [native code] } ×没有该属性 AT_TARGET 2 2 2 ×没有该属性 BUBBLING_PHASE 3 3 3 ×没有该属性 CAPTURING_PHASE 1 1 1 ×没有该属性 webkitDirection
InvertedFromDevice ×没有该属性 false ×没有该属性 ×没有该属性 wheelDeltaY ×没有该属性 -120 ×没有该属性 ×没有该属性 wheelDeltaX ×没有该属性 0 ×没有该属性 ×没有该属性 webkitMovementY ×没有该属性 0 ×没有该属性 ×没有该属性 webkitMovementX ×没有该属性 0 ×没有该属性 ×没有该属性 charCode ×没有该属性 0 ×没有该属性 ×没有该属性 clipboardData ×没有该属性 undefined ×没有该属性 ×没有该属性 initWebKitWheelEvent ×没有该属性 function initWebKitWheelEvent() { [native code] } ×没有该属性 ×没有该属性 NONE 0 0 ×没有该属性 ×没有该属性 MOUSEDOWN 1 1 ×没有该属性 ×没有该属性 MOUSEUP 2 2 ×没有该属性 ×没有该属性 MOUSEOVER 4 4 ×没有该属性 ×没有该属性 MOUSEOUT 8 8 ×没有该属性 ×没有该属性 MOUSEMOVE 16 16 ×没有该属性 ×没有该属性 MOUSEDRAG 32 32 ×没有该属性 ×没有该属性 CLICK 64 64 ×没有该属性 ×没有该属性 DBLCLICK 128 128 ×没有该属性 ×没有该属性 KEYDOWN 256 256 ×没有该属性 ×没有该属性 KEYUP 512 512 ×没有该属性 ×没有该属性 KEYPRESS 1024 1024 ×没有该属性 ×没有该属性 DRAGDROP 2048 2048 ×没有该属性 ×没有该属性 FOCUS 4096 4096 ×没有该属性 ×没有该属性 BLUR 8192 8192 ×没有该属性 ×没有该属性 SELECT 16384 16384 ×没有该属性 ×没有该属性 CHANGE 32768 32768 ×没有该属性 ×没有该属性 rangeParent [object HTMLDivElement] ×没有该属性 ×没有该属性 ×没有该属性 rangeOffset 0 ×没有该属性 ×没有该属性 ×没有该属性 isChar false ×没有该属性 ×没有该属性 ×没有该属性 mozMovementX 1168 ×没有该属性 ×没有该属性 ×没有该属性 mozMovementY 576 ×没有该属性 ×没有该属性 ×没有该属性 mozPressure 0 ×没有该属性 ×没有该属性 ×没有该属性 mozInputSource 1 ×没有该属性 ×没有该属性 ×没有该属性 initNSMouseEvent function initNSMouseEvent() { [native code] } ×没有该属性 ×没有该属性 ×没有该属性 axis 2 ×没有该属性 ×没有该属性 ×没有该属性 initMouseScrollEvent function initMouseScrollEvent() { [native code] } ×没有该属性 ×没有该属性 ×没有该属性 originalTarget [object HTMLDivElement] ×没有该属性 ×没有该属性 ×没有该属性 explicitOriginalTarget [object HTMLDivElement] ×没有该属性 ×没有该属性 ×没有该属性 preventBubble function preventBubble() { [native code] } ×没有该属性 ×没有该属性 ×没有该属性 preventCapture function preventCapture() { [native code] } ×没有该属性 ×没有该属性 ×没有该属性 getPreventDefault function getPreventDefault() { [native code] } ×没有该属性 ×没有该属性 ×没有该属性 RESET 65536 ×没有该属性 ×没有该属性 ×没有该属性 SUBMIT 131072 ×没有该属性 ×没有该属性 ×没有该属性 SCROLL 262144 ×没有该属性 ×没有该属性 ×没有该属性 LOAD 524288 ×没有该属性 ×没有该属性 ×没有该属性 UNLOAD 1048576 ×没有该属性 ×没有该属性 ×没有该属性 XFER_DONE 2097152 ×没有该属性 ×没有该属性 ×没有该属性 ABORT 4194304 ×没有该属性 ×没有该属性 ×没有该属性 ERROR 8388608 ×没有该属性 ×没有该属性 ×没有该属性 LOCATE 16777216 ×没有该属性 ×没有该属性 ×没有该属性 MOVE 33554432 ×没有该属性 ×没有该属性 ×没有该属性 RESIZE 67108864 ×没有该属性 ×没有该属性 ×没有该属性 FORWARD 134217728 ×没有该属性 ×没有该属性 ×没有该属性 HELP 268435456 ×没有该属性 ×没有该属性 ×没有该属性 BACK 536870912 ×没有该属性 ×没有该属性 ×没有该属性 TEXT 1073741824 ×没有该属性 ×没有该属性 ×没有该属性 ALT_MASK 1 ×没有该属性 ×没有该属性 ×没有该属性 CONTROL_MASK 2 ×没有该属性 ×没有该属性 ×没有该属性 SHIFT_MASK 4 ×没有该属性 ×没有该属性 ×没有该属性 META_MASK 8 ×没有该属性 ×没有该属性 ×没有该属性 SCROLL_PAGE_UP -32768 ×没有该属性 ×没有该属性 ×没有该属性 SCROLL_PAGE_DOWN 32768 ×没有该属性 ×没有该属性 ×没有该属性 MOZ_SOURCE_UNKNOWN 0 ×没有该属性 ×没有该属性 ×没有该属性 MOZ_SOURCE_MOUSE 1 ×没有该属性 ×没有该属性 ×没有该属性 MOZ_SOURCE_PEN 2 ×没有该属性 ×没有该属性 ×没有该属性 MOZ_SOURCE_ERASER 3 ×没有该属性 ×没有该属性 ×没有该属性 MOZ_SOURCE_CURSOR 4 ×没有该属性 ×没有该属性 ×没有该属性 MOZ_SOURCE_TOUCH 5 ×没有该属性 ×没有该属性 ×没有该属性 MOZ_SOURCE_KEYBOARD 6 ×没有该属性 ×没有该属性 ×没有该属性 HORIZONTAL_AXIS 1 ×没有该属性 ×没有该属性 ×没有该属性 VERTICAL_AXIS 2 ×没有该属性 ×没有该属性 ×没有该属性

对照表格内容,可以看到,鼠标滚动事件与点击事件有很多类似的地方。比方说兼容部分:event.type, event.screenX/event.screenY, event.clientX/event.clientY, event.altKey, event.shiftKey, event.cancelBubble都是一样的,不兼容的部分,IE6-8的event.srcElement与其他浏览器的event.target.

进口的苹果分外甜,滚轮事件显然也是有额外的差异的,想想也知道,是与滚轮相关的,也是我们实际应用最常用的。

在除了FireFox之外的浏览器下,滚动的上下滚动与否是下面这个-event.wheelDelta(//zxx: 本文发布后补充:Delta读音对应希腊字母△,形状就像三角裤,因此,wheelDelta可以记做“滚轮的三角裤”):
event.wheelDelta与滚动示意

根据自己的测试,在我的win7系统下,无论IE7, IE10, Opera12,或者是safari5.1,每次往下滚动event.wheelDelta值都是-120. //zxx:网上有说法说Safari值为-360, 我对此表示怀疑,下图为我的测试截图。
Safari浏览器下wheelDelta截图

对于FireFox浏览器(Opera浏览器也有),判断鼠标滚动方向的属性为event.detail, 向下滚动值为3.
FireFox浏览器下event.detail, 值为3

需要注意的是,FireFox浏览器的方向判断的数值的正负与其他浏览器是相反的。FireFox浏览器向下滚动是正值,而其他浏览器是负值。

三、兼容的滚轮事件方法

知己知彼百战百胜,知道了差异就知道如何处理这些差异。毕竟不是写JS库,我们这里只处理滚动方向这块的差异。

整合我们通常事件添加方法,于是有(下代码代号为addEvent.js):

/**
 * 简易的事件添加方法
 */
 
define(function(require, exports, module) {
    exports.addEvent = (function(window, undefined) {        
        var _eventCompat = function(event) {
            var type = event.type;
            if (type == 'DOMMouseScroll' || type == 'mousewheel') {
                event.delta = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3;
            }
            //alert(event.delta);
            if (event.srcElement && !event.target) {
                event.target = event.srcElement;    
            }
            if (!event.preventDefault && event.returnValue !== undefined) {
                event.preventDefault = function() {
                    event.returnValue = false;
                };
            }
            /* 
               ......其他一些兼容性处理 */
            return event;
        };
        if (window.addEventListener) {
            return function(el, type, fn, capture) {
                if (type === "mousewheel" && document.mozFullScreen !== undefined) {
                    type = "DOMMouseScroll";
                }
                el.addEventListener(type, function(event) {
                    fn.call(this, _eventCompat(event));
                }, capture || false);
            }
        } else if (window.attachEvent) {
            return function(el, type, fn, capture) {
                el.attachEvent("on" + type, function(event) {
                    event = event || window.event;
                    fn.call(el, _eventCompat(event));    
                });
            }
        }
        return function() {};    
    })(window);        
});

于是,我们就可以很从容使用mousewheel事件了。例如:

addEvent(dom, "mousewheel", function(event) {
    if (event.delta < 0) { alert("鼠标向上滚了!"); }
});

四、简单的实例、上面方法验证

本想做个完备的幻灯平滑移动效果(左右有点击按钮之类),结果一不小心,都凌晨了,于是,改变主意了,就只做了个鼠标滚动,图片列表左右移动的效果。您可以狠狠地点击这里:滚轮事件下图片列表左右滑动demo

鼠标放在图片列表区域上,鼠标滚轮下滚滚,上滚滚,就可以看到图片列表们左右平滑移动的效果了。

鼠标滚轮事件下的平滑切换效果

其中的滚轮相关交互就是使用的上面exports暴露的addEvent方法。

相关代码实现如下,下面这个展示的就是平滑移动的核心代码们(代号为slide.js):

/**
 * 简易的列表左右滑动切换效果
 * 鼠标事件是关键,因此,一些数值写死在方法中,纯测试用
 */
 
define(function(require, exports, module) {
    var Event = require("/study/201304/addEvent.js");
    var _move = function(ele, to, from) {
        // 动画实现
        // ...
    };
    return {
        index: 0,
        visible: 4,
        init: function(box) {
            // box指滚动的列表容器
            var self = this
              , length = box.getElementsByTagName("li").length;
            Event.addEvent(box.parentNode, "mousewheel", function(event) {
                 if (event.delta > 0 && self.index > 0) {
                    // 往上滚
                    self.index--;
                 } else if (event.delta < 0 && self.index < length - self.visible) {
                     // 往下
                     self.index++;                     
                 } else {
                    return; 
                 }
                 _move(box, -1 * self.index * 140);    
             
                 event.preventDefault();
            });
        }
    };
});
 

原理很简单,滚轮改变,索引改变,也就是列表的最终位置改变,动画到目标位置即可。

然后,demo页面使用seajs简单调用就可以了!

var $ = function(id) {
    return document.getElementById(id);
};
seajs.use("/study/201304/slide.js", function(slide) {
    slide.init($("slideBox"));
});

就结束了,一些具体细节,例如关于HTML部分,或者动画的实现等,可以去demo等查看代码展示。

不过从效果来看,IE6以及IE7浏览器下的滚动并没有hold页面的滚动条,多番其他尝试也是如此,希望可以有相关经验的同行指点下,优化IE7/IE7浏览器下的体验效果。

原本还想再添加一个自定义滚动条的demo的,一看时间,我勒个去,已经1:11:11了,好不吉利的数字啊,看了下程序员运势万年历,今天不适宜写demo。于是,结语睡觉。

五、首尾呼应的结语

正常状态鼠标滚轮相关东西,我现在各个细节历历在目,要是现在发我张卷子,考鼠标滚轮事件知识,没有个90分我自己都不信。然而,目前为止,自己并未刻意去记忆,因此,如果接下来的1年时间自己很少或不接触相关内容。估计到时别人一问,小心脏一慌,说不定就一下子想不起"DOMMouseScroll"这厮了。因此,在结尾处,我要给自己来个特殊记忆。

怎么记呢?恩……啊,抓狂了,想不出来。event.wheelDelta/event.detail

wheelDelta→滚轮的三角裤,火狐是骚狐狸,没有这个三角裤?欲知详情,请看火狐?

……得,睡了,梦里再想吧~~

(本篇完)1f44d.svg 是不是学到了很多?可以分享到微信
1f44a.svg 有话要说?点击这里


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK