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

时间:2012-03-08 06:10:37

标签: javascript

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

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

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

8 个答案:

答案 0 :(得分: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毫秒就会发生一次。

答案 1 :(得分:36)

查看Underscore.js库的“节流”方法。

http://underscorejs.org/#throttle

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

答案 2 :(得分:10)

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

CREATE OR REPLACE FUNCTION timelog()
  RETURNS trigger AS
$BODY$
DECLARE
t_ix real;
n int;

BEGIN
IF NEW.time_type = 'Start' THEN
    SELECT t.time_index FROM table_ebscb_spa_log04 t WHERE t.fn_name = NEW.fn_name AND t.time_type = 'Start' ORDER BY t.stmtserial DESC LIMIT 1 INTO t_ix;
      GET DIAGNOSTICS n = ROW_COUNT;
        IF (n = 0) THEN 
        t_ix := 1; --this is ok, here it's really necessary to set it to 1
        ELSE
        t_ix := t_ix + 1;
        END IF;

ELSE 
    IF NEW.time_type = 'Lap' THEN 
        SELECT t.time_index  FROM table_ebscb_spa_log04 t WHERE t.fn_name = NEW.fn_name AND (t.time_type = 'Start' OR time_type = 'Lap') ORDER BY t.stmtserial DESC LIMIT 1 INTO t_ix;
          GET DIAGNOSTICS n = ROW_COUNT;
            IF (n = 0) THEN 
            t_ix := 1; --!!!HERE I MADE A MISTAKE (SORRY) IT SHOULDN'T BE SET TO 1, IT SHOULD THROW AN ERROR MESSAGE 'There isn't any previous same fn_name with time_type Start';
            ELSE 
            t_ix := t_ix + 0.1;
            END IF;
  END IF;
END IF;
NEW.time_index = t_ix;
return NEW;
END
$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;
ALTER FUNCTION timelog()
  OWNER TO postgres;

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

答案 3 :(得分: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);
    }
}

答案 4 :(得分: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-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));

答案 5 :(得分: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。调整价值以满足您的需求。

答案 6 :(得分:0)

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

答案 7 :(得分:0)

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

  • 自上次通话以来,如果该功能超过 interval 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”的键,并将其设置为所需的毫秒数。