What’s a sane way to throttle scroll handlers without missing the last event?

What’s up everyone? I’m wiring up a scroll-driven pixel-art parallax thing and trying to keep it smooth, but my throttled handler sometimes skips the final position so the UI “snaps” a beat later.

function throttle(fn, wait) {
  let last = 0;
  let trailingArgs = null;
  let timer = null;

  return function (...args) {
    const now = performance.now();
    const remaining = wait - (now - last);

    if (remaining <= 0) {
      last = now;
      fn.apply(this, args);
    } else {
      trailingArgs = args;
      if (!timer) {
        timer = setTimeout(() => {
          timer = null;
          last = performance.now();
          fn.apply(this, trailingArgs);
          trailingArgs = null;
        }, remaining);
      }
    }
  };
}

How do you structure throttle so it stays responsive but guarantees the final scroll state gets applied without causing extra layout thrash?

Hari

The bug is the stale trailing timer. Keep overwriting the latest args, clear and reschedule the timeout, and make sure the trailing call always uses the newest scroll position.

function throttle(fn, wait) {
  let last = 0;
  let timer = null;
  let lastArgs;
  let lastThis;

  function invoke() {
    timer = null;
    last = performance.now();
    fn.apply(lastThis, lastArgs);
    lastArgs = lastThis = null;
  }

  return function (...args) {
    const now = performance.now();
    const remaining = wait - (now - last);

    lastArgs = args;
    lastThis = this;

    if (remaining <= 0) {
      if (timer) clearTimeout(timer);
      invoke();
      return;
    }

    clearTimeout(timer);
    timer = setTimeout(invoke, remaining);
  };
}

For scroll work, keep the handler mostly to scrollY reads, then batch DOM writes in requestAnimationFrame. That keeps the final state from snapping in late and avoids extra layout churn.

WaffleFries