wheel 滚轮事件
一、事件说明
当滚动鼠标滚轮或操作其它类似输入设备时会触发滚轮事件
Tips:
- 使用触摸板触发该事件,由于系统的平滑处理,会触发多次该事件
- IE 下无法使用触摸板触发该事件
- 替换了已被弃用的非标准 mousewheel 和 DOMMousewheel 事件。
二、兼容性
桌面端
浏览器 | 版本 |
---|---|
Chrome | 61 |
Edge | 12 |
Firefox | 17 |
IE | 9 |
Opera | 48 |
Safari | 7 |
移动端
浏览器 | 版本 |
---|---|
WebView Android | 61 |
Chrome Android | 61 |
Firefox for Android | 17 |
Opera Android | 45 |
Safari on iOS | 7 |
Samsung Internet | 8 |
三、常规用法(不考虑触摸板的情况)
// wheel事件回调
const onWheel = useCallback(
e => {
console.log(e)
},
[]
);
useEffect(() => {
window.addEventListener('wheel', onWheel);
return function () {
window.removeEventListener('wheel', onWheel);
};
}, []);
四、加上防抖
上面的做法虽然可以实现功能,但是每滚动一下就会处理一次逻辑,很浪费性能而且在IE上面还会导致滚动卡顿不流畅,这个时候就可以加上防抖的效果再尝试一下。
const { current} = useRef({
isScroll: true, // 是否可以滚动
timer: null, // setTimeout句柄
delay: 300 // 防抖时间
})
// wheel事件回调
const onWheel = useCallback(
e => {
// 是否可以滚动,不写到setTimeout里面就可以先执行逻辑,在进行防抖,避免造成不跟手的情况
if (current.isScroll) {
current.isScroll = false;
...
}
// 如果已经上次的防抖效果还没有释放,又触发了wheel回调,那么就取消上次的防抖效果
if (current.timer) {
clearTimeout(current.timer);
}
// 重新开始计算
current.timer = setTimeout(() => {
current.isScroll = true;
}, current.delay);
},
[]
);
useEffect(() => {
window.addEventListener('wheel', onWheel);
return function () {
window.removeEventListener('wheel', onWheel);
};
}, []);
五、考虑触摸板的情况
上面的那种方法,已经可以解决大部分的情况,但是如果防抖的时间设置的少于一定时间,用上触摸板之后就会发现,轻轻的滑动一次,wheel事件的处理逻辑就会被触发好多次。
这种情况产生的原因,根据网上的说法是似乎系统为了实现平滑滚动的效果还是其他原因,当你手指离开触摸板后wheel依然还会执行一段时间。
解决办法
- 增加防抖的延迟时间。这个办法虽然可以解决触摸板滑动一次触发多次wheel事件的问题,但是可能会造成正常的滚动时页面根本滚不动的问题(即在防抖的时间内又触发了一次)
- 判断本次wheel事件是由系统的平滑处理所产生还是由正常的滚动所产生的,但是wheel事件的回调当中并没有提供可以区分这两种情况的属性,这个时候只能找一下正常滚动时的回调属性和由系统的平滑处理产生的回调属性这两者有什么不同了。
根据对比可以发现,由系统的平滑处理所产生事件的移动距离基本都是小于100的,而且事件产生的时间间隔都很短,所以可以通过判断上次正常滚动的时间和本次事件产生的时间间隔还有本次事件的移动距离来判断,本次的事件是不是一次正常的滚动(并不是最优质的解决办法,还会有误判发生,但目前并没有想到更好的解决办法)。
// 获取时间戳(毫秒级别)
const getTime = useCallback(() => {
return new Date().getTime();
}, []);
const { current } = useRef({
isScroll: true, // 是否可以正常的滚动
scrollTime: null, // 上次切换的时间戳(毫秒级别)
timer: null, // setTimeout句柄
interval: 500, // 两次滑动之间的最小事件间隔
distance: 100, // 有效滑动的最小距离
delay: 400, // 防抖时间
});
// wheel事件处理函数
const wheel = useCallback(
e => {...},
[]
);
// wheel事件回调
const onWheel = useCallback(
e => {
const time = getTime();
const y = e.deltaY; // 以垂直滚动为列
if (current.scrollTime) {
// 如果这次触发的时机跟上次切换的时机差大于一定时间,并且滑动距离大于一定距离,那么就认为是有效滑动
if (
time - current.scrollTime > current.interval &&
Math.abs(y) >= current.distance
) {
// 触摸板滚动
wheel(e);
clearTimeout(current.timer);
current.isScroll = false;
current.scrollTime = time;
}
}
if (current.isScroll) {
// 正常滚动
wheel(e);
current.scrollTime = getTime();
current.isScroll = false;
}
if (current.timer) {
clearTimeout(current.timer);
}
current.timer = setTimeout(() => {
current.isScroll = true;
}, current.delay);
},
[]
);
useEffect(() => {
window.addEventListener('wheel', onWheel);
return function () {
window.removeEventListener('wheel', onWheel);
};
}, []);