【JavaScript(webapi)】记一次Debug,关于 removeEventListener 方法莫名失效
先上一段代码
这个函数用于以反比例曲线平滑滚动页面:
这里用闭包实现是为了能如下图这样方便地 在添加回调事件的同时传参:
但是这个函数存在一个影响用户体验的滚动问题,上动图:
我在页面自动滚动快结束的时候(有时候就想快点翻),向下滑动鼠标滚轮,然而这个函数却是不到目标位置不罢休。
由于我向下滚动略过了锚定位置,而 Interval 并没有捕捉到路过锚定的状态,所以页面反而会向上滚动。
解决方法很简单,我立刻想到了 event.preventDefault() 。
我只要在开始动画前监听 mousewheel 事件,然后在回调函数中写下这一行来阻止默认行为即可。
当然,动画结束时一定要 removeEventListener 清除监听。
经过修改后,这个函数是这样的:
注:关于 addEventListener 方法的第三个参数{ passive: false },用于成功调用 e.preventDefault().
详情移步 → https://blog.csdn.net/csdnXiaoZou/article/details/87276026,这位博主写的很详细
上面的代码很简单,
当动画结束时,清除当前定时器,并移除当前 mousewheel 回调事件。
当新动画开始时,清除上一个定时器,并移除上一个 mousewheel 回调事件。
然后就出了问题,当我连续触发两次动画后,滚动鼠标滚轮就没用了,同时控制台在滚动时输出1。
这代表上一个回调事件并没有被正确清除。
于是我下断点跟着走了一遍,代码确实跑对了地方,那问题就出在闭包中。
分析了半天,还是没有头绪。万般无奈之下,开始查文档,百度 + Google。
查资料的时候突然看到一句话,大概意思是,必须保证 add 和 remove 时的函数指针相同。
这我当然是早就知道的,但我思考了一下,问题只能出现在这里,于是试着调试了一下函数指针
输出:
恍然大悟,每次调用函数都重新定义了一遍 stop_scroll ,于是后一次的 stop_scroll 与前一次并不相同。
那么只要把函数拿出来就好了。
于是改成这样:
问题解决,但总感觉这样不够优雅,毕竟我是封装了一个函数,这多出来一个函数算怎么回事?
于是我又试着改了改,这样就可以了:
函数内声明一个全局变量来存这个回调函数。
至此完美解决!
还有一个思路,这里直接强硬地禁止用户滚动页面,而实际上我们可以当用户滚动滚轮时直接停止动画。
另一个,再参考大多数网站的做法,即滚动位置不依赖于当前的 window,pageYOffset,而是自己维护一个变量,记录这一步该到哪了。
加了一个参数,代表滚动过程中禁止用户滚动还是用户滚动即停。
接口优雅了许多,但函数一点也不优雅…
甚至有些地方用到了两层的闭包,但是也是为了防止写更多代码(小声bb… addEventListener 没有给出传参的方法)
代码:
//...
function scroll_smooth(y, velocity, interval, callback, stopScroll = true) {
if (stopScroll) {
if (typeof stop_scroll === 'undefined') {
stop_scroll = function(e) {// 事件回调函数
e.preventDefault();
};
}
} else {
if (typeof get_state === 'undefined') {
var fun_list = function() {
var state = false; // false 用户未滚动
return [
function() {
return state;
},
function(state_) {
return function() {
console.log(state);
state = state_;
}
}
];
}();
var get_state = fun_list[0];
var set_state = fun_list[1];
}
}
function animate(y, velocity, interval, callback) {
if (stopScroll) {
document.addEventListener('mousewheel', stop_scroll, {
passive: false
});
} else {
temp_fun = set_state(true);
document.addEventListener('mousewheel', temp_fun);
}
this.timer = setInterval(() => {
if (!stopScroll) {
if (get_state() === true) { // 判断用户滚动
clearInterval(this.timer);
document.removeEventListener('mousewheel', temp_fun);
callback && callback();
return;
}
}
if (window.pageYOffset != y) { // 滚动过程
var step = (y - window.pageYOffset) / velocity
window.scroll(0, window.pageYOffset + (step < 0 ? Math.floor(step) : Math.ceil(step)));
} else {
callback && callback();
clearInterval(this.timer);
if (stopScroll) {
document.removeEventListener('mousewheel', stop_scroll);
}
}
}, interval);
}
return function() {
clearInterval(this.timer);
if (stopScroll) { // 清除事件回调
document.removeEventListener('mousewheel', stop_scroll);
} else {
if (typeof temp_fun !== 'undefined') {
document.removeEventListener('mousewheel', temp_fun);
}
}
animate(y, velocity, interval, callback);
};
}