JS滚动事件触发两次?两种方案解决
2025-01-21 16:18:00
滚动事件触发两次的问题
使用 JavaScript 或 jQuery 为网页创建自动滚动到下一屏的功能时,有时会遇到滚动事件触发两次的问题。这个问题通常发生在页面滚动动画完成后,浏览器会再次触发滚动事件,导致动画执行两次,进而影响用户体验。本文将分析问题原因,并提供有效的解决方案。
问题分析
代码中 $(window).scroll()
监听了窗口滚动事件。 当用户手动滚动或者使用 $('html').animate()
进行动画滚动时,都会触发此事件。关键在于,animate()
改变滚动位置之后,又触发了 scroll
事件监听函数,因此出现了第二次执行。由于滚动是一个持续的过程,它可能会连续触发多次滚动事件,所以我们用到了 setTimeout
限制执行频率,在设定的延迟时间(例子中的 50 毫秒)内只执行最后一次 scroll
事件的逻辑。但仅仅使用 setTimeout
并不足以完全避免动画滚动引起的二次触发问题,因为它只是减少而不是消除。
解决方案一: 使用标志位
通过引入一个标志变量来控制 scroll
事件处理函数的执行状态。当动画开始执行时,设置该标志为 true
;动画结束时,设置该标志为 false
。在滚动事件触发时,首先判断标志位状态,只有当标志位为 false
时,才执行相应的逻辑,防止重复触发。
操作步骤:
- 声明一个名为
isAnimating
的变量,初始值为false
。 - 在
scroll
事件处理函数开始时,先检查isAnimating
是否为true
。若是,则直接返回,不执行后面的代码。 - 在
$('html').animate()
方法执行之前,设置isAnimating
为true
。 - 在
animate()
回调函数中(即动画执行完成后),设置isAnimating
为false
。
代码示例:
var content = 0;
var scrolled = 0;
var contentNum = $('.content').length;
var offset = $("#intro").offset();
var lastScrollTop = 0;
var isAnimating = false;
var timeout;
$(window).scroll(function() {
if (isAnimating) {
return;
}
var st = $(this).scrollTop();
var scrolled = 0;
clearTimeout(timeout);
timeout = setTimeout(function() {
if (st > lastScrollTop){
scrolled = 1;
} else {
scrolled = -1;
}
if ( !(content == 0 && scrolled == -1) && !(content == (contentNum-1) && scrolled == +1) ){
content = content + scrolled;
offset = $(".content").eq(content).offset();
isAnimating = true; //设置动画开始标志
$('html').animate({
scrollTop: offset.top,
scrollLeft: offset.left
}, 500, function(){
isAnimating = false; //设置动画结束标志
});
}
lastScrollTop = st;
}, 50);
});
这个方法有效地防止了自动滚动引发的第二次滚动事件,而且逻辑简单容易理解。
解决方案二: 事件节流 (Throttling)
节流的概念在于在一定时间内,不管触发了多少次事件,只执行一次函数。setTimeout
本质上可以用来做简单节流,但是更好的做法是建立一个独立的节流函数,在 scroll
函数中,通过节流函数来包裹需要执行的代码,这是一种更为通用的方式。这种方式同样也可以降低滚动处理函数的触发频率,同时还可以避免因快速滚动导致的多次 setTimeout
叠加。
操作步骤:
- 创建一个
throttle
函数,该函数接受一个函数func
和一个delay
(延迟时间)作为参数。 - 在
throttle
函数中,创建一个timer
变量存储定时器的 id,并在返回的函数中检查timer
是否存在,若存在,则表示正在执行,直接跳过;不存在则表示可以执行。 - 在节流处理函数中,创建一个延迟计时器,并在其完成时清空定时器
timer=null
。 - 在
scroll
事件中, 使用throttle
函数处理滚动操作。
代码示例:
var content = 0;
var scrolled = 0;
var contentNum = $('.content').length
var offset = $("#intro").offset();
var lastScrollTop = 0;
function throttle(func, delay) {
let timer = null;
return function(...args) {
if (timer) {
return;
}
timer = setTimeout(() => {
func.apply(this, args);
timer = null;
}, delay);
};
}
const throttledScroll = throttle(function(){
var st = $(this).scrollTop();
var scrolled = 0;
if (st > lastScrollTop){
scrolled = 1;
} else {
scrolled = -1;
}
if ( !(content == 0 && scrolled == -1) && !(content == (contentNum-1) && scrolled == +1) ){
content = content + scrolled;
offset = $(".content").eq(content).offset();
$('html').animate({
scrollTop: offset.top,
scrollLeft: offset.left
}, 500);
}
lastScrollTop = st;
}, 100)
$(window).scroll(throttledScroll);
通过throttle
函数的控制,可以更加精确地控制函数执行的时间,降低滚动事件触发次数,并提升滚动处理的流畅度。throttle
函数属于公共方法,可用于其他任何需要节流的场景。
安全建议
在处理滚动事件时,需要注意以下几点:
- 避免过度的动画: 滚动动画应该平滑且短暂,长时间动画可能会引起用户的不适,甚至可能导致浏览器卡顿。
- 处理边界情况: 确保滚动动画在页面的顶部和底部正确停止,不会无限滚动或产生跳动。
- 移动设备兼容性: 注意移动设备上
scroll
事件的特性,尤其是在一些旧版本的移动浏览器上可能表现略有差异,需要做好相应的测试。
正确理解和处理 scroll
事件,能为用户提供良好的浏览体验,并提升网页的整体性能。