返回

JS滚动事件触发两次?两种方案解决

javascript

滚动事件触发两次的问题

使用 JavaScript 或 jQuery 为网页创建自动滚动到下一屏的功能时,有时会遇到滚动事件触发两次的问题。这个问题通常发生在页面滚动动画完成后,浏览器会再次触发滚动事件,导致动画执行两次,进而影响用户体验。本文将分析问题原因,并提供有效的解决方案。

问题分析

代码中 $(window).scroll() 监听了窗口滚动事件。 当用户手动滚动或者使用 $('html').animate() 进行动画滚动时,都会触发此事件。关键在于,animate() 改变滚动位置之后,又触发了 scroll 事件监听函数,因此出现了第二次执行。由于滚动是一个持续的过程,它可能会连续触发多次滚动事件,所以我们用到了 setTimeout 限制执行频率,在设定的延迟时间(例子中的 50 毫秒)内只执行最后一次 scroll 事件的逻辑。但仅仅使用 setTimeout 并不足以完全避免动画滚动引起的二次触发问题,因为它只是减少而不是消除。

解决方案一: 使用标志位

通过引入一个标志变量来控制 scroll 事件处理函数的执行状态。当动画开始执行时,设置该标志为 true;动画结束时,设置该标志为 false。在滚动事件触发时,首先判断标志位状态,只有当标志位为 false 时,才执行相应的逻辑,防止重复触发。

操作步骤:

  1. 声明一个名为 isAnimating 的变量,初始值为 false
  2. scroll 事件处理函数开始时,先检查 isAnimating 是否为 true。若是,则直接返回,不执行后面的代码。
  3. $('html').animate() 方法执行之前,设置 isAnimatingtrue
  4. animate() 回调函数中(即动画执行完成后),设置 isAnimatingfalse

代码示例:

        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 叠加。

操作步骤:

  1. 创建一个 throttle 函数,该函数接受一个函数 func 和一个 delay(延迟时间)作为参数。
  2. throttle 函数中,创建一个 timer 变量存储定时器的 id,并在返回的函数中检查 timer 是否存在,若存在,则表示正在执行,直接跳过;不存在则表示可以执行。
  3. 在节流处理函数中,创建一个延迟计时器,并在其完成时清空定时器 timer=null
  4. 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 函数属于公共方法,可用于其他任何需要节流的场景。

安全建议

在处理滚动事件时,需要注意以下几点:

  1. 避免过度的动画: 滚动动画应该平滑且短暂,长时间动画可能会引起用户的不适,甚至可能导致浏览器卡顿。
  2. 处理边界情况: 确保滚动动画在页面的顶部和底部正确停止,不会无限滚动或产生跳动。
  3. 移动设备兼容性: 注意移动设备上 scroll 事件的特性,尤其是在一些旧版本的移动浏览器上可能表现略有差异,需要做好相应的测试。

正确理解和处理 scroll 事件,能为用户提供良好的浏览体验,并提升网页的整体性能。