Hatena::ブログ(Diary)

latest log このページをアンテナに追加 RSSフィード

2009-07-22

日食 と uuAltCSS.js と CSS3 multiple background image

f:id:uupaa:20090722204939p:image

http://uupaa-js-spinoff.googlecode.com/svn/trunk/uuAltCSS.js/demo/box-shadow/solid_version/multibg1.htm

色々と制限はありますが、CSS3 の multiple background image を uuAltCSS.js に実装してみました。

日食の画像と、モノリス(?)みたいなステージと、ブラウザアイコンが背景画像です。


色々

  • CSS3 の multiple background image を CSS2.1 しか解釈しないブラウザに食わせると、宣言が無視されます背景が真っ白になります
  • Firefox3.5用のコードを入れ忘れたので、Firefox3.5では動きません。

2009-07-21

uuAltCSS.js の現状

http://uupaa-js-spinoff.googlecode.com/svn/trunk/uuAltCSS.js/demo/box-shadow/solid_version/demo.htm

IE6+, Firefox2+, Safari3.1+, Chrome1+, Opera9.5+ で動きます。

このデモで使っている CSS3 プロパティ

です。

現実的な速度で動作する段階まできました。

なにがなんだか良くわからない方は、Safari4 や Google Chrome3 で上記のページを開き、IE7 や Firefox3 の画面と見比べてみてください。

2009-07-20

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

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

function onresize() {
  (何か)
}
window.attachEvent("onresize", onresize);

解決していきましょう。

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

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)

f:id:uupaa:20090720230619p:image

まとめ

エージェント作戦は、IE7, IE8 では特にデメリットもないようですし、IE6 では 65ms(15%) の改善がみられます。

さらに高負荷なページで計測すると、最大で 40% ほどのレスポンスの改善が見られるケースもありました。

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

resize agentの実際のコード


反省会

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

2009-07-19

IE6 で borderColor="transparent" が機能しない

uuAltCSS+.js のコードリード用のエントリです。コードを知らない方には無意味なエントリです。

border-radius: 用の処理に、「ボーダー部分をcanvasで描画し、元々borderが設定されていたノードの borderColor を透明にする」という処理がありますが、IE6 は borderColor="transparent" が機能しません(colorが赤なら、赤いボーダーが描画されてしまう)

単純に、border = "0px none" としてしまうと、再描画時に ノード の幅/高さを求める処理で問題が発生します(ボーダー幅がなくなったため、元々のサイズより小さく再描画されてしまう)。

これを回避するため以下のコードを追加しました。

処理の概要は

  • borderColor = "transparent" と同じように振舞うには、マージンにボーダー幅を含めた状態で描画する
  • ただし幅/高さの再計算の直前に、元の状態(ボーダーが設定されている状態)に戻し要素のサイズを再計算する。再計算が終わったら、border = "0px none" を設定する。

です。

function boxshadow() {
  (略)
+   if (_ie6) {
+     // オリジナルの値を保存する
+     prop.ie6borderorg = {
+       marginTop: cs.marginTop,
+       marginLeft: cs.marginLeft,
+       marginRight: cs.marginRight,
+       marginBottom: cs.marginBottom,
+       borderTopWidth: cs.borderTopWidth,
+       borderLeftWidth: cs.borderLeftWidth,
+       borderRightWidth: cs.borderRightWidth,
+       borderBottomWidth: cs.borderBottomWidth
+     };
+     // 上書用の値を事前に作成しておく
+     prop.ie6borderfix = {
+       marginTop: (prop.NMT + prop.NBT) + "px",
+       marginLeft: (prop.NML + prop.NBL) + "px",
+       marginRight: (prop.NMR + prop.NBR) + "px",
+       marginBottom: (prop.NMB + prop.NBB) + "px",
+       border: "0px none"
+     };
+   }
  (略)
}

function boxshadowRecalc() {
  var nodeProp = _plan.boxshadow, v, i = 0, iz = nodeProp.length,
      prop;

  for (; i < iz; i += 2) {
    v = nodeProp[i]; // node
    prop = v.uuAltCSSBoxShadow;
    // update rect
+   if (_ie6) { // restore border and margin state
+     _mm.mix(v.style, prop.ie6borderorg);
+   }
    prop.nodeRect = _style.getRect(v);
    prop.viewRect = _style.getRect(prop.view);
    prop.nodeOffset = getOffsetFromAncestor(v, prop.view);
    boxshadowDraw(v, prop, 1);
  }
}

function boxshadowDraw(node, pr, redraw) {
  (略)
  // --- gradient ---
  if (pr.gradtype) {
    (略)
  }
  if (pr.radius) {
    (略)
    if (!redraw) {
      // bg setting
      node.style.backgroundColor = TRANSPARENT;
      node.style.backgroundImage = "none";
-     if (_ie6) {
-       // IE6 bug 'borderColor = "transparent";' unsupported
-       node.style.paddingTop = pr.NBT + pr.NPT;
-       node.style.paddingLeft = pr.NBL + pr.NPL;
-       node.style.paddingRight = pr.NBR + pr.NPR;
-       node.style.paddingBottom = pr.NBB + pr.NPB;
-       node.style.border = "0px none";
-     } else {
-       node.style.borderColor = TRANSPARENT;
-     }
+     node.style.borderColor = TRANSPARENT;
+   }
+   if (_ie6) {
+     // IE6 'borderColor = "transparent";' unsupported
+     _mm.mix(node.style, pr.ie6borderfix);
    }
  }
  (略)
}

コード追加前(before)

上記のコードを追加する前はこのように画像の下が身切れる現象が発生していました。

f:id:uupaa:20090719010106j:image

この現象は以下の条件が重なった場合に発生します。

  • IE6
  • ページ表示直後の一度だけ発生する
  • nodeの親にボーダーが設定されていない
  • nodeにwidthが明示されていない
  • nodeがスクロールして表示される位置(ページの下方)に設置されている

また、描画中にリドロー(再描画)も発生しています。

この状態は、window サイズを拡大/縮小することでリセットできます。

f:id:uupaa:20090719010108j:image

コード追加後(after)

対策コード追加後は以下のようになります。

f:id:uupaa:20090719010109j:image

他のブラウザと同様のレンダリングになり、身切れる現象は回避できましたが、今度はリドローすると、要素の高さが若干変化(増えたり減ったり)する新たな現象が発生するようになりました。

f:id:uupaa:20090719010107j:image

こちらについては有効な対策方法が現在見つかっていないため、宿題です。

ロジックの見直しを行い、上記制限はなくなりました。

2009-07-17

Opera Dragonfly で特定のコードが含まれているとコードビューアが正しく動作しない

f:id:uupaa:20090717023742p:image

Operaの開発者ツール(Dragonfly)で、JavaScriptに特定のコードが含まれていると、コードビューアが「スクロールにコードが追従しなくなる」不具合を起こします(上記画像の白くなってる部分には、本当はコメント行が表示されてるはず)

また、任意の位置にブレイクポイントが設定できなくなります。

Opera9.5 - Opera10β2 で発生します。

現象が発生する HTML(df.htm) と JavaScript(df.js) は以下になります。HTMLはダミーです。

<!doctype html><html><head><title></title>
<script src="df.js"></script>
</head><body></body></html>
(function() {
var COMMENT = /\/\*[^*]*\*+([^\/][^*]*\*+)*\//g,
    AT_IMPORT =
        /@import\s*(?:url)?[\("']+\s*([\w\.\/\+\-]+)\s*["'\)]+\s*([\w]+)?\s*;/g;

//
//
//
//
//
//
//
//
//
//
//

//  '" />';

//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//

})();

途中のコメント行を以下のようにしても現象は発生します。

//  '" ' + '/>';

df.jshttp://code.google.com/p/uupaa-js-spinoff/source/browse/trunk/src/uuAltCSS.js?r=257 を最小化したものです。

このページ自体をバグ報告しておきました。