IE の window.resize イベントの問題を回避する

IE の window.resize イベントは、いくつかの問題を抱えています。

function onresize() {
  (何か)
}
window.attachEvent("onresize", onresize);
  • うっかり無限ループする(無限に再描画が走る)
  • 重い(特にIE6)

解決していきましょう。

無限ループ/無限リドロー問題

resize イベントハンドラの中で、document.body.innerWidht, innerHeight が変化するような操作を行うと、再度 resize イベントが発生し無限ループする現象が発生します(無限リロリロ)。

無限リロリロを回避するために、resize イベントハンドラ内でイベントをデタッチし、再アタッチを繰り返す方法があります(リアタッチ作戦)

function onresize() {
  window.detachEvent("onresize", onresize); // デタッチ
  (何か)
  window.attachEvent("onresize", onresize); // リアタッチ
}
window.attachEvent("onresize", onresize);

動作速度の問題

リアタッチ作戦は一見いけそうにみえますが、魔王(IE6)の前では所詮子供だましのようです。
この作戦を繰り返し行うと、「ある時点から resize イベント発生時にどんどん重くなる」という別の現象が発生します。
イベントをアタッチ ⇒ デタッチを繰り返すのは止めて、時間差によるイベントの集約を行います(一人時間差作戦)

var _globalLock = 0;

function resize() {
  if (!_globalLock++) {
    setTimeout(function() {
      (コールバック)
      setTimeout(function() { _globalLock = 0; }, 0); // delay unlock
    }, 40);
  }
}

さらに前へ

一人時間差作戦を組み込んだコードを走らせていると、今度は欲がでてきます「もっと速く/軽くできないか」。
プロファイラを走らせると、resize イベント自体が「とても重い」ということに気がつきます。体感でも resize イベントが多発する状況では目に見えて重くなります。

そこで、resize イベントを使用せず同様のことを実現してみます。

var _globalLock = 0;
var _size = { w: 0, h: 0 };
var _ie = document.uniqueID;
var _quirks = (document.compatMode || "") !== "CSS1Compat";
var _ieroot = _quirks ? "body" : "documentElement";

function getInnerSize() {
  var root = _ie ? document[_ieroot] : window;
  return { w: root.innerWidth  || root.clientWidth,
           h: root.innerHeight || root.clientHeight };
}

// resize agent
function agent() {
  function loop() {
    if (!_globalLock++) {
      var size = getInnerSize();
      if (_size.w !== size.w || _size.h !== size.h) { // resized
        _size = size; // update
        (コールバック)
      }
      setTimeout(function() { _globalLock = 0; }, 0); // delay unlock
    }
    setTimeout(loop, 100);
  }
  setTimeout(loop, 100);
}

100ms ごとにブラウザのクライアント領域の大きさ(innerWidth, innerHeight)を監視するエージェントを用意し、変化があれば、コールバックします(エージェント作戦)

計ってみた

box-shadow: をレンダリングする JavaScript を埋め込んだページで速度を計ってみました。

IE6 + ASPIRE ONE(XP)
IE7 + HP G5000(VISTA)
IE8 + DELL VOSTRO 1000(XP)


まとめ

エージェント作戦は、IE7, IE8 では特にデメリットもないようですし、IE6 では 65ms(15%) の改善がみられます。
さらに高負荷なページで計測すると、最大で 40% ほどのレスポンスの改善が見られるケースもありました。

一人時間差作戦も簡潔に書けるので悪くはありません(良くも…無いかな)。

resize agentの実際のコード

反省会

  • 無限リロリロ, リアタッチ作戦, 一人時間差作戦, エージェント作戦
    • 造語多し