检测用户是否已从顶部滚动

时间:2015-10-21 10:36:58

标签: javascript jquery html

显而易见的答案是简单地将事件附加到滚动事件:

var scrolled = false;

$(window).scroll(function() {
   if($(window).scrollTop() > 0) {
       scrolled = true;
   } else {
       scrolled = false;
   }
});

然而,jQuery的创建者John Resig's blogpost from 2011表示:将处理程序附加到窗口滚动事件是一个非常非常糟糕的主意。

并建议如下:

var didScroll = false;

$(window).scroll(function() {
    didScroll = true;
});

setInterval(function() {
    if ( didScroll ) {
        didScroll = false;
        // Check your page position
    }
}, 250);

过去五年有什么变化吗? John Resig的解决方案仍然是最好的吗?

3 个答案:

答案 0 :(得分:5)

我讨厌与jQuery的创造者争论,但拥有一个聪明的头脑并不一定意味着你总是正确的。在滚动上使用延迟可能会破坏基于事件的任何动画。我花了大量的时间来研究滚动事件并根据它制作插件和演示,这似乎是一种城市神话,它经常触发大多数设备。

这是一个测试数量的小型演示 - 最大似乎是显示刷新率:

Codepen

除非浏览器启用了平滑滚动,否则鼠标滚动或平移只会导致单个事件被触发。即使情况并非如此,大多数机器都能很好地处理帧速率允许的调用次数 - 只要网页的其余部分构建良好。

最后一点是最大的问题,浏览器可以在很小的时间间隔内处理相当多的脚本,但是最可能减慢执行速度的是标记错误。因此,最大的瓶颈往往是重新粉刷。使用开发人员工具时可以很好地检查这一点。

其中一些示例包含固定位置元素和动画的大块,这些块不是基于transform,而是使用像素值。两者都会触发不必要的重绘,这会对性能产生巨大影响。这些问题与滚动火灾的频率没有直接关系。

使用固定位置元素,可以通过此hack防止webkit相关浏览器上的重新绘制:

.fixed {
  -webkit-backface-visibility: hidden;
}

这会创建一个单独的堆叠顺序,使内容独立于其周围环境,并防止整个页面被重新绘制。 Firefox似乎默认情况下这样做,但遗憾的是我还没有找到适用于IE的属性,所以最好在任何情况下避免使用这些元素,特别是如果它们的大小是视口的很大一部分。

在撰写上述文章时尚未实施以下内容。

那将是requestAnimationFrame,它现在拥有非常好的浏览器支持。这样,可以以不强制浏览器的方式执行功能。因此,如果滚动处理程序中存在复杂的函数,那么使用它将是一个非常好的主意。

另一个优化是使用标记,除非需要,否则不运行任何操作。一个有用的工具可以是添加类并检查它们的存在。这是我通常使用的方法:

$(function() {

var flag, modern = window.requestAnimationFrame;

$(window).scroll(function() {

    if (!$(this).scrollTop()) {

        if (flag) {
        if (modern) requestAnimationFrame(doSomething);
        else doSomething();
        flag = false;
        }
    }
    else if (!flag) {

        if (modern) requestAnimationFrame(doMore);
        else doMore();
        flag = true;
    }
});

function doSomething() {

    // special magic
}

function doMore() {

    // other shenanigans
}
});

如何切换类的示例可用于确定布尔值:

$(function() {

var modern = window.requestAnimationFrame;

$(window).scroll(function() {

    var flag = $('#element').hasClass('myclass');

    if (!$(this).scrollTop()) {

        if (flag) {
        if (modern) requestAnimationFrame(doSomething);
        else doSomething();
        $('#element').removeClass('myclass');
        }
    }
    else if (!flag) {

        if (modern) requestAnimationFrame(doMore);
        else doMore();
        $('#element').addClass('myclass');
    }
});

function doSomething() {

    // special magic
}

function doMore() {

    // other shenanigans
}
});

如果它不会干扰任何所需的功能,那么对事件进行去抖动当然是一种很好的方法。它可以很简单:

$(function() {

var doit, modern = window.requestAnimationFrame;

$(window).scroll(function() {

    clearTimeout(doit);

    doit = setTimeout(function() {

        if (modern) requestAnimationFrame(doSomething);
        else doSomething();

    }, 50);
});

function doSomething() {

    // stuff going on
}
});

在评论中,Kaiido有一些有效的观点。除了必须提高定义超时的变量的范围之外,这种方法在实践中可能不太有用,因为它只会在滚动完成后执行函数。我想参考this interesting article,得出的结论是,比在这里进行去抖动更有效的是限制 - 确保事件只在每个时间单位运行函数最大量,更接近问题中提出的内容。 Ben Alman为此做了a nice little plugin(注意它已经在2010年写了)。

Example

我设法只提取了限制所需的部分,它仍然可以以类似的方式使用:

$(window).scroll($.restrain(50, someFunction));

其中第一个参数是仅一个事件将执行第二个参数的时间量 - 回调函数。这是底层代码:

(function(window, undefined) {

var $ = window.jQuery;

$.restrain = function(delay, callback) {

    var executed = 0;

    function moduleWrap() {

        var elapsed = Date.now()-executed;

        function runIt() {

            executed = Date.now();
            callback.apply(this, arguments);
        }

        if (elapsed > delay) runIt();
    }

    return moduleWrap;
};
})(this);

要执行的函数内部会发生什么事情仍然会减慢当然的过程。应始终避免的一个例子是使用.css()设置样式并在其中放置任何计算。这对性能非常不利。

还必须提到,当在某个点向下页面而不是在顶部切换时,标记代码更有意义,因为该位置不会多次触发。因此,在问题范围内可能会遗漏对标志本身的检查。

这样做,在页面加载时检查滚动位置并在此基础上切换是有意义的。 Opera对此特别顽固 - 它在页面加载后恢复缓存的位置。所以小超时最适合:

$(window).on('load', function() {

    setTimeout(function() {

        // check scroll position and set flag

    }, 20);
});

一般情况下我会说 - 不要避免使用滚动事件,它可以在网页上提供非常好的效果。只要保持警惕它们如何粘在一起。

答案 1 :(得分:1)

我在这里使用的解决方案:

function bindScrollObserverEvent() {
    var self = this;
    $.fn.scrollStopped = function(callback) {
        var $this =$(this),
            self = this;
        $this.scroll(function() {
            if ($this.data('scrollTimeout')) {
                clearTimeout($this.data('scrollTimeout'));
            }
            $this.data('scrollTimeout', setTimeout(callback, 250, self));
        });
    };

        $(window).scrollStopped(function() {
          //You code
         });

答案 2 :(得分:0)

这两个片段是不同的:第一个片段仅检查我们是否位于页面顶部,而第二个片段检查我们是否已在页面上滚动(无论我们的位置)最后250毫秒和只有这样,执行我们的脚本。

我个人认为这是一个比这更好的解决方案"建议"就像评论中提出的那样,debounce your event 一个小例子:

var scrolling, timeout;

function callback(){
  // do something
  }

$(window).scroll(function(){
  if(!scrolling){
    // first time in last 250 ms we did scroll
    scrolling = true;
    callback();
    timeout = setTimeout(function(){scrolling = false}, 250);
    }
  else{
    // we already scrolled less than 250ms ago, reset the timeout
    clearTimeout(timeout);
    timeout = setTimeout(function(){scrolling = false}, 250);
    }
});

这样,如果你在同一个位置停留一个小时,你就不会一次调用你的功能,而在建议的片段中,你会称之为3600000次。

你在这里做的只是设置一个250ms的超时并在每个滚动事件上重置它。这可能发生得很快,但是,即使我没有任何关于它的论文,我也非常有信心这不会阻止任何现代设备或js翻译。

对于第一个片段,嗯......它确实只是用于检测我们是否处于最佳位置,即使我怀疑它的用处,我也看不出如何真正改进它。 ..除非将scrolled更改为notAtTop。但正如@AlexBerd指出的那样,它在开始时甚至不应该被设置为假,因为我们不知道加载时的滚动位置所以它可能应该是var notAtTop = $(window).scrollTop() > 0;