滚动事件触发次数过多。我只希望它每秒最多发射一次

问题描述 投票:53回答:8

我有一个“无限滚动”的页面。它会计算页面末尾与当前页面之间的差异,如果此差异足够小,则会加载更多内容。使用jQuery代码是这样的:

$(window).on('scroll', function() {
    if (window.pageYOffset > loadMoreButton.offsetTop - 1000)
        # load more content via ajax
}

现在,问题在于每次滚动时,每个滚动都会触发此事件多次。我想每隔x毫秒发射一次。我该怎么做?

javascript
8个回答
36
投票

查看Underscore.js库的“油门”方法。

http://underscorejs.org/#throttle

它给出的示例正是您所要求的 - 限制您处理滚动事件的频率。


37
投票

解决此问题的一种方法是定义时间间隔,并且仅在该时间间隔内处理一次滚动事件。如果在该时间间隔内有多个滚动事件进入,则忽略它并仅在该时间间隔过去时处理它。

var scrollTimer, lastScrollFireTime = 0;

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

    var minScrollTime = 100;
    var now = new Date().getTime();

    function processScroll() {
        console.log(new Date().getTime().toString());
    }

    if (!scrollTimer) {
        if (now - lastScrollFireTime > (3 * minScrollTime)) {
            processScroll();   // fire immediately on first scroll
            lastScrollFireTime = now;
        }
        scrollTimer = setTimeout(function() {
            scrollTimer = null;
            lastScrollFireTime = new Date().getTime();
            processScroll();
        }, minScrollTime);
    }
});

这将立即触发第一个滚动事件,然后在滚动条移动时每100ms大约一次滚动事件,然后在滚动条停止移动后返回一个最终事件。您可以通过将参数更改为setTimeout(当前设置为100)来调整事件的频率。

这里有一个演示:http://jsfiddle.net/jfriend00/EBEqZ/,您需要打开调试控制台窗口,开始在内容窗口中移动滚动条,然后在调试控制台窗口中观察每个滚动事件的时间。在我的Chrome版本中,它们的最小间距设置为100毫秒,它们似乎每100-200毫秒就会出现一次。


10
投票

来自jQuery的创建者John Resig有一个很酷的解释来解决这种情况。

var outerPane = $details.find(".details-pane-outer"),
    didScroll = false;

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

setInterval(function() {
    if ( didScroll ) {
        didScroll = false;
        // Check your page position and then
        // Load in more results
    }
}, 250);

来源:http://ejohn.org/blog/learning-from-twitter/


8
投票
var isWorking = 0;

$(window).on('scroll', function()
{
    if(isWorking==0)  
    {
         isWorking=1;
         if (window.pageYOffset > loadMoreButton.offsetTop - 1000)
         # load more content via ajax
         setTimeout(function(){isWorking=0},1000);
    }
}

5
投票
var now = new Date().getTime();
$(window).scroll( function () {
    if (window.pageYOffset > loadMoreButton.offsetTop - 1000)
    {
        if (new Date().getTime() - now > 1000)
        {
            console.log("Task executed once per second");
            now = new Date().getTime();
        }
    }
});

要么

您可以使用Throttling fonction调用:throttling-function-calls

function throttle(fn, threshhold, scope) {
  threshhold || (threshhold = 250);
  var last,
      deferTimer;
  return function () {
    var context = scope || this;

    var now = +new Date,
        args = arguments;
    if (last && now < last + threshhold) {
      // hold on to it
      clearTimeout(deferTimer);
      deferTimer = setTimeout(function () {
        last = now;
        fn.apply(context, args);
      }, threshhold);
    } else {
      last = now;
      fn.apply(context, args);
    }
  };
}

你可以这样称呼它:

$('body').on('mousemove', throttle(function (event) {
  console.log('tick');
}, 1000));

4
投票

这是一个不需要使用额外JS库或插件的解决方案,旨在简化。它可能没有其他实现那么高效,但它绝对是每次滚动时触发主事件的一步。

这是由Danny Van Kooten从这个blog post拍摄的。在我的博客上,我用它来延迟我的onscroll()事件以获得我的回到顶部按钮。

var timer;
$(window).scroll(function() {
    if(timer) {
        window.clearTimeout(timer);
    }
    timer = window.setTimeout(function() {
       // actual code here. Your call back function.
    console.log( "Firing!" );
    }, 100);
});

您还可以通过将变量移出回调函数来进一步提高性能,以避免不必要的重新计算,例如$(window).height()的值或某些静态div元素的高度,一旦页面加载就不会更改。

这是一个根据我的用例改编的例子。

var scrollHeight = $("#main-element").height(); //never changes, no need to recalculate.
$(window).on('scroll', function() {
    if (timer) 
        window.clearTimeout(timer);
    timer = window.setTimeout(function() {
        var scrollPosition = $(window).height() + $(window).scrollTop();    
        if ($(window).scrollTop() < 500)
            $(".toggle").fadeIn(800);
        else 
            $(".toggle").fadeOut(800);
    }, 150); //only fire every 150 ms.
});

这将实际功能限制为仅每150ms执行一次,或者如果150ms未通过则将计时器重置为0。调整价值以满足您的需求。


0
投票

滚动火焰多次是正确的,你应该每次都能以不同的方式获得滚动位置。我认为你需要设置一个定时器,当你第一次进入滚动事件时,如你提到的x毫秒,并记录时间戳,然后下一次滚动事件触发,检查最后一次触发时间并忽略它,如果它在x毫秒内,并在你的Timer动作中做真正的工作。


0
投票

人们不需要大量的局部变量来获得合适的油门功能。节流功能的目的是减少浏览器资源,而不是应用你正在使用的更多开销。作为这种说法的证据的证据,我设计了一种油门功能,其范围内只有5个“悬挂”变量参考。另外,我对油门功能的不同用途要求它们有许多不同的情况。这是我认为'好'油门功能需要的东西列表。

  • 如果自上次调用以来已经超过间隔MS,则立即调用该函数。
  • 避免执行另一个间隔MS的功能。
  • 延迟过多的事件触发而不是完全放弃事件。
  • 在连续调用时更新延迟事件对象,使其不会变为“陈旧”。

并且,我相信以下节流功能满足所有这些。

function throttle(func, alternateFunc, minimumInterval) {
    var executeImmediately = true, freshEvt = null;
    return function(Evt) {
        if (executeImmediately) { // Execute immediatly
            executeImmediately = false;
            setTimeout(function(f){ // handle further calls
                executeImmediately = true;
                if (freshEvt !== null) func( freshEvt );
                freshEvt = null;
            }, minimumInterval);
            return func( Evt );
        } else { // Delayed execute
            freshEvt = Evt;
            if (typeof alternateFunc === "function") alternateFunc( Evt );
        }
    };
}

然后,围绕DOM事件侦听器包装此限制函数:

var ltCache = [];
function listen(obj, evt, func, _opts){
    var i = 0, Len = ltCache.length, lF = null, options = _opts || {};
    a: {
        for (; i < Len; i += 4)
            if (ltCache[i] === func &&
              ltCache[i+1] === (options.alternate||null) &&
              ltCache[i+2] === (options.interval||200)
            ) break a;
        lF = throttle(func, options.alternate||null, options.interval||200);
        ltCache.push(func, options.alternate||null, options.interval||200, lF);
    }
    obj.addEventListener(evt, lF || ltCache[i+3], _opts);
};
function mute(obj, evt, func, options){
    for (var i = 0, Len = ltCache.length; i < Len; i += 4)
        if (ltCache[i] === func &&
          ltCache[i+1] === (options.alternate||null) &&
          ltCache[i+2] === (options.interval||200)
        ) return obj.removeEventListener(evt, ltCache[i+3], options);
}

用法示例:

function throttle(func, alternateFunc, minimumInterval) {
    var executeImmediately = true, freshEvt = null;
    function handleFurtherCalls(f){
        executeImmediately = true;
        if (freshEvt !== null) func( freshEvt );
        freshEvt = null;
    };
    return function(Evt) {
        if (executeImmediately) { // Execute immediatly
            executeImmediately = false;
            setTimeout(handleFurtherCalls, minimumInterval);
            return func( Evt );
        } else { // Delayed execute
            freshEvt = Evt;
            if (typeof alternateFunc === "function") alternateFunc( Evt );
        }
    };
}
var ltCache = [];
function listen(obj, evt, func, _opts){
    var i = 0, Len = ltCache.length, lF = null, options = _opts || {};
    a: {
        for (; i < Len; i += 4)
            if (ltCache[i] === func &&
              ltCache[i+1] === (options.alternate||null) &&
              ltCache[i+2] === (options.interval||200)
            ) break a;
        lF = throttle(func, options.alternate||null, options.interval||200);
        ltCache.push(func, options.alternate||null, options.interval||200, lF);
    }
    obj.addEventListener(evt, lF || ltCache[i+3], _opts);
};
function mute(obj, evt, func, options){
    for (var i = 0, Len = ltCache.length; i < Len; i += 4)
        if (ltCache[i] === func &&
          ltCache[i+1] === (options.alternate||null) &&
          ltCache[i+2] === (options.interval||200)
        ) return obj.removeEventListener(evt, ltCache[i+3], options);
}
var numScrolls = 0, counter = document.getElementById("count");
listen(window, 'scroll', function whenbodyscrolls(){
    var scroll = -document.documentElement.getBoundingClientRect().top;
    counter.textContent = numScrolls++;
    if (scroll > 900) {
      console.log('Body scrolling stoped!');
      mute(window, 'scroll', whenbodyscrolls, true);
    }
}, true);
<center><h3>\/ Scroll Down The Page \/</h3></center>
<div style="position:fixed;top:42px"># Throttled Scrolls: <span id="count">0</span></div>
<div style="height:192em;background:radial-gradient(circle at 6em -5em, transparent 0px, rgba(128,0,0,.4) 90em),radial-gradient(circle at 10em 40em, rgba(255,255,255,.8) 0px, rgba(128,0,0,.02) 50em),radial-gradient(circle at 4em 80em, rgba(0,192,0,.75) 0px,rgba(0,128,0,.56) 10em,rgba(255,0,96,.03125) 30em),radial-gradient(circle at 86em 24em, rgba(255,0,0,.125) 0px,rgba(0,0,255,.0625) 60em,transparent 80em);"></div>
<style>body{margin:0}</style>

默认情况下,此功能限制为每200ms最多一次调用。要将间隔更改为不同的毫秒数,请在options参数中传递名为“interval”的键,并将其设置为所需的毫秒数。

© www.soinside.com 2019 - 2024. All rights reserved.