読者です 読者をやめる 読者になる 読者になる

しまてく

学んだ技術を書きためるブログ

ブラウザの再描画のタイミング

最近測度にシビアなものを作っていてはまってた問題がやっとこ解決したのでメモ。


その問題というのは、JavaScriptでヒットテストをするときの速度がどうしても速くならないということ。


条件は

    1. このプログラムが動くのはIEのみ。
    2. ブラウザ上に任意の数(上限は150個)のアイテム(divタグ)が存在する。
    3. このdivタグにはName="item"という属性があるとする。

動作は

    1. ユーザがマウスのドラッグで任意の矩形を指定する。
    2. 指定された矩形の中に存在するアイテム(divタグ)を判定する。
    3. 判定の最初に無条件で「selected」というclassを削除する。
    4. ヒットしたアイテムに対して「selected」というclassを追加する。

という感じ。


無駄な処理をすべて省いて、高速化に良いといわれてる方法を数々取り入れ。。。
それでも1ループ200msec程度かかってしまいました。
(※ログを出力するようにしてたので実際よりもすごく時間がかかってます。)

このときのソースを簡単に書くと↓みたいな感じ

var elms = document.getElementsByName("item");

for( var i = 0, l = elms.length; i < l; i++ ){
    var elm = elms[i];
    elm.className = elm.className.replace("selected", "");

    //ここでelmのoffsetTop/Left/Height/Widthを取得

    //さらにelmが矩形の中に存在するか判定する条件式
    if("ヒットした場合"){
        elm.className += " selected";
    }
}

コレだと選択し終わってからアイテムに「selected」classが反映されて描画されるまでのレスポンスが遅いです。*1


そこでネット上の情報をむさぼってるとこんな文章を見つけました。

■再フローの回数をできるだけ減らそう

スクリプトが再描画や再フローにつながる操作の必要に迫られることは多い. 
アニメーションは根本から再フローだ.しかも引き続き望まれている. 
このように再フローはウェブ開発の肝の一つになっている. 
スクリプトを高速化するには, 同じ効果の得られる最小限に再フローを留める必要がある.

ブラウザは再フローをする前にスクリプトのスレッドが終わるのを待つことがある. 
Opera は十分な変更が起こるか十分な時間が経つまで待つし, スレッドの終わりに来るまで待つこともある. 
したがって, 同じスレッド内で十分に素早く変更を済ませれば, 再フローは一回だけ起きて済むかもしれない. 
ただし, この挙動に依存することはできない. 特に Opera の動作している異なる速度の機器を考慮すると無理がある. 

引用「http://www.hyuki.com/yukiwiki/wiki.cgi?EfficientJavaScript#i16」


原文はこちら

■Keeping the number of reflows to a minimum

There are many times that a script will need to do something that will trigger a repaint or reflow. 
Animations are built on reflows, and these will continue to be desired. 
So reflows are a fact of Web development, and to keep scripts running fast, they should be kept to 
a minimum while still having the same overall effect.

Browsers may choose to wait until the end of a script thread before reflowing to show the changes. 
Opera will wait until enough changes have been made, enough time has elapsed, or the end of the thread is reached. 
This means that if the changes happen quickly enough in the same thread, they may only produce one reflow. 
However, this cannot be relied on, especially considering the various different speeds of devices that Opera runs on.

引用「http://dev.opera.com/articles/view/efficient-javascript/?page=3#reflow」


んん〜、期待しちゃダメって書いてあるけど一応やってみよう。

var elms = document.getElementsByName("item");

var hitElms = [];

//当たり判定だけをするループ
for( var i0 = 0, l0 = elms.length; i0 < l0; i0++ ){
    var elm = elms[i0];
    //elm.className = elm.className.replace("selected", ""); //ここでクラスを変えるのはやめた

    //ここでelmのoffsetTop/Left/Height/Widthを取得

    //さらにelmが矩形の中に存在するか判定する条件式
    if("ヒットした場合"){
        //elm.className += " selected"; //ここでクラスを変えるのはやめた
        hitElms.push( elm );
    }
}

//クラスを消すだけのループ
for( var i1 = 0, l1 = elms.length; i1 < l1; i1++ ){
    var elm = elms[i1];
    elm.className = elm.className.replace("selected", "");
}

//クラスを追加するだけのループ
for( var i2 = 0; l2 = hitElms.length; i2 < l2; i2++ ){
    var elm = elms[i2];
    elm.className += " selected";
}

こんな感じにコードを書き換えたら劇的に早くなりました!
なんというか、「普通の」アプリになったなぁという印象w


コードだけ見ると無駄にループを回していて、アホな感じに見えるんですが
実際IEでは描画が本当に1回になったんじゃないだろうかと思うくらいの高速化。


できればこんなコード書きたくないよ><
jQueryでスマートに書きたい><><*2

*1:ログ出力抜きで体感1.5sec〜2sec

*2:始めはすべてjQueryで「もっとスマートに」書いていたんですが、どうも遅いのでココだけは直書きしました。