素人がプログラミングを勉強していたブログ

プログラミング、セキュリティ、英語、Webなどのブログ since 2008

連絡先: すかいぷ:javascripter_  か javascripter あっと tsukkun.net skypeのほうがいいです

scrollイベントが呼ばれすぎることへの対処

onscrollを監視していると大量にeventが来てパフォーマンスが劣化するというのはよく聞く話で、 underscore.jsなどでイベントをthrottleやdebounceさせて対処していることが多い。 ただ、scrollに応じてスタイルを変更させたりする場合は、0.1秒くらい遅れがあると視覚的にカクカクしがちで、仕方なくsetTimeout(fn, 0)でthrottleさせることも多いと思う。

scrollイベントは画面のフラッシュレートと同期していないので、マウスやトラックパッドの動きに応じて大量に呼ばれていて、setTimeoutも画面のフレッシュレートとは関係なく内部の実行タイマーに応じて動いている。よって、setTimeout(fn, 0)でthrottleしてもまだ無駄が多い。実験してみたところ、requestAnimationFrameを使うと少しだけマシになることがわかった。

結論から言うと、スクロールと画面の同期遅れを最小限にしたい場合はsetTimeoutではなくrequestAnimationを使ったほうがsetTimeout(fn, 0)より良くて、画面のチラツキが気にならないような場面ではsetTimeout(fn, 100)くらいを使うと良いと思う。

[Log] native: 484
[Log] throttled: 429
[Log] smartly throttled: 341

これは、下記のテストコードを適当なページで走らせて上下にスクロールさせた結果。

setInterval(function () {
  console.log("native: %d", nNative);
  console.log("throttled: %d", nThrottled);
  console.log("smartly throttled: %d", nSmartThrottled);
  
}, 1000);

var nNative = 0;

window.addEventListener('scroll', function () {
    nNative++;
}, false);

var tid;
var nThrottled = 0;
window.addEventListener('scroll', function (event) {
  clearTimeout(tid);
  tid = setTimeout(function () {
    nThrottled++;
  }, 0);
}, false);


var nSmartThrottled = 0;

function callback(e) {
  nSmartThrottled++;
}

var frameId;
function handleScroll(event) {
  cancelAnimationFrame(frameId);
  frameId = requestAnimationFrame(function () {
    callback(event);
  });
}


window.addEventListener('scroll', handleScroll, false);