DOMを高速に操作するための skin.js というライブラリを作った

(タイトル修正 DOMを高速に操作 => DOMの値を高速に更新 at Sat Mar 09 2013 15:30:09 GMT+0900 (JST))
(Skin#inject実装したのでタイトル元に戻した at Mar 09 2013 18:10:04 GMT+0900 (JST))

若手の会で、JavaScript Hell on Earth というテーマで話してきました。
js_hell_on_earth http://www.rvl.io/mizchi/js_hell_on_earth


というわけでDOM抽象ラッパーつくりました。

mizchi/skin.js · GitHub https://github.com/mizchi/skin.js

目的

クライアントサイドJSでは一回書いたら終わり、ではありません。ゲームなどのリッチなユーザー体験を提供する際、高頻度でDOMを書き換える際に、HTMLElementでキャッシュを持って高速に書き換えられるようにしたいという需要がありました。
そして、その際には簡単なDOMの対応をとるテンプレートエンジン(ただし他のテンプレートエンジンの邪魔をしない)ようなものが欲しくなるわけです。

使い方

初期化

new Skin(HTMLElement_or_CSSSelector [, html] )

挿入

Skin#set(key, val)
Skin#set({key:val})


特定のHTMLElement、もしくはそのセレクタをルートにして、newします。第二引数では、初期化時に渡すHTMLを指定出来ます(省略可)

この例では script type='text/template' でテンプレートとして初期化して渡しています。
値を書き換えたいDOMに対しては、data-value='hoge' のように、HTMLに対して属性を付与して、Skin#setで対応するものを書き換えます。

index.html

<div id='goblin'></div>
<script type="text/template" id='status_template'>
  <div class='monster'>
    <span data-value='name'></span>
    <span data-value='hp'></span>
    <span data-value='mp'></span>
  </div>
</script>
window.onload = function(){
  var root = document.querySelector('#goblin');
  var html = document.querySelector('#status_template').innerText;
  var goblin_skin = new Skin(root, html);
  goblin_skin.set({
    name: 'goblin',
    hp: 100,
    mp: 20
  });
};

その結果、こうなります。

<div id='goblin'>
  <div class='monster'>
    <span data-value='name'>goblin</span>
    <span data-value='hp'>100</span>
    <span data-value='mp'>20</span>
  </div>
</div>

Skin#inject

(追加 at Mar 09 2013 17:27:26 GMT+0900 (JST))

HTMLElementを列挙しながら関数を適用します。

goblin_skin.inject('mp', function(el){el.style.visibility = 'hidden'})

もちろんjQuery使ってもいいです

goblin_skin.inject('mp', function(el){$(el).hide();})

data-valueが同じ物を複数持っている可能性があるので、高階関数で。

skin.jsがやること

高速な値の書換

初期化後はクエリを全く投げないので早いです。

シンプルなテンプレート

data-value属性をつけるだけで値との対応が取れます。

skin.jsがやらないこと

特定のモデルとの対応

渡す側でやれ

エスケープなどのバリデーション

渡す側でやれ

イベントフックなどの複雑なデータ加工

渡す側でやれ

イテレーティブなデータ挿入

渡す側でやれ

同値挿入の無視

渡す側でやれ(いれてもいいかなと思ってるが、モデルの値を持ちたくない)

素早い初期化

セマンティクスの観点からdata-valueを使っています。
data属性でクエリを投げているので、初期化が若干遅いです。このライブラリは、ルートから全検索して値を挿入するものではありません。あくまで領域を絞って初期化して、特定のDOM以下で高速に値を書き換えることを目的にしています。

たとえば次のような状況で真価を発揮します。

var skin = new Skin('#timer');
setInterval(function(){
  skin.set("unixtime", Date.now());
}, 1000/60);

毎秒60回 Unixtimeを更新します。(たぶんいけますがこれでもCPUは食います)

fork me!

この実装は、実はcoffeeで50行しかありません。どちらかというと、テンプレートを一括で書いてしまうサーバーサイド的発想から脱却するための提案みたいなものです。

世の中のJSは、setIntervalのループの中からモデルの特定の値を配信する際、非効率なテンプレーティングをしているものが多いです。そのようなものは、激しいCPU処理を発生させ、UIをブロックし「タッチにしにくい」といった状況を引き起こす原因になります。とくにスマホは計算資源が限られています。

特にHTML5のゲームでは、UIレイヤーをHTMLで書きつつも、FPSを出すためにレンダリングのコストは最小である必要があります。


テンプレートエンジンを持たないBackboneのヘルパとして作ったので、機能は最小限ですが、BackboneどころかjQueryにも依存してないので、どんなライブラリにも混ぜて使えます。*1


mizchi/skin.js · GitHub https://github.com/mizchi/skin.js


僕が言いたいことは以上です。

*1:テンプレートエンジン持ちのライブラリとは役割が被るので相性が悪いと思われる