显而易见的答案是简单地将事件附加到滚动事件:
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的解决方案仍然是最好的吗?
答案 0 :(得分:5)
我讨厌与jQuery的创造者争论,但拥有一个聪明的头脑并不一定意味着你总是正确的。在滚动上使用延迟可能会破坏基于事件的任何动画。我花了大量的时间来研究滚动事件并根据它制作插件和演示,这似乎是一种城市神话,它经常触发大多数设备。
这是一个测试数量的小型演示 - 最大似乎是显示刷新率:
除非浏览器启用了平滑滚动,否则鼠标滚动或平移只会导致单个事件被触发。即使情况并非如此,大多数机器都能很好地处理帧速率允许的调用次数 - 只要网页的其余部分构建良好。
最后一点是最大的问题,浏览器可以在很小的时间间隔内处理相当多的脚本,但是最可能减慢执行速度的是标记错误。因此,最大的瓶颈往往是重新粉刷。使用开发人员工具时可以很好地检查这一点。
其中一些示例包含固定位置元素和动画的大块,这些块不是基于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年写了)。
我设法只提取了限制所需的部分,它仍然可以以类似的方式使用:
$(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;