重たいUIライブラリ2007年08月31日 03時16分47秒

Zend入門も書かずにJSネタ。ごめんなさい。がんばります。

UIライブラリの選定

いまかかってる仕事で、フォームの入力支援なんかで多少リッチなUIを使いたかったのでライブラリを検討していた。スクラッチしてるヒマはなさそうだからな。クライアント側で若干のデータ操作なんかもやりたいのでprototype.jsを使うことを基本にして、UIライブラリもその延長線上での検討となった。

もともと入力支援より以前にちょっとだけエフェクトを使いたかったのでscript.aculo.usも試してみようと思っていたところ、ちょうどprototype.js + script.aculo.us なリッチUIライブラリが見つかった。それがspinelzだ。

デモを見たところ、そこそこUIはそろっていて見た目もそんなに悪くなく、なによりprototype.jsとのコンフリクトを気にしなくてよい(あたりまえだが)ため、導入してみることにした。

まぁ、入れては見たものの

最初に使ってみたのが郵便番号の部分検索で複数候補がある場合のモーダルダイアログに適用してみた。あまり苦労せず使えたので「なかなかよいかも」と思っていた(デザインのカスタマイズはめんどくさそう。画像とCSS両方だからな)。

次に日付入力で、おあつらえ向きに「DatePicker」が用意されているので試してみた。

残念ながら、というか、表示位置はクラスに食わせる要素を直接制御する必要があり、また座標算出系のヘルパーもないため、prototype.jsのPositionとか使いながら自前で位置合わせを行うことになってなんだかなぁ、という感じ。

さらに月や年単位で前後に移動できるのだが、なんとなくもっさりしている。いや、そんなレベルではなく3呼吸くらい待たされる感じだった。それでも「まぁ、なんとか使えるかな」と甘いことを考えていた。

ただ、他のページに比べて、DatePickerを導入しているページの表示が、コンテンツがロードされてから少し待たされるのが気になってはいたのだが...

使い方が悪いっちゃそうなんだけど

おととい手がけてた部分が、入力フォーム中に日付入力が8つ(!)もあり、バカ正直にそれぞれに対して1つづつDatePickerをバインドしてみた。ページロード完了が相変わらず重いが、まぁとりあえずできたし良しとしよう。

ところが、他のJS部分の動作確認やレイアウトの確認のためIEで確認(普段はFirefoxでのみ確認してる)してみたら、ページロードがこれでもかってなくらい重たい。もー重たい。どうにも重たい

さすがにこれは実用にならんと思ってボトルネックを調べたところ、どうもDatePickerのコンストラクタ(というかinitialize)からの一連の流れが原因だった模様。

もう少しマシな実装してよ...

コードを追っかけていったところ、初期化や表示月の変更など表示内容を更新する必要がある局面で、すべて新しくDOMノードの構築を行っているという、かなりひどい実装になっていた。なるほど月を前後するときに重たいワケだ。

DOM操作でtableを構築するのはDHTMLの中でもかなりの高コストなのだが、配置した要素を使いまわすことなく愚直に再構築するとは、とてもUIライブラリとして公開しているシロモノの実装とは思えない

重たいのはIEとFirefoxで、OperaやSafariといった比較的「軽量」と呼ばれるブラウザは実用に耐える速度だったのだが、これらはいかんせんマイナー路線。一番利用者が多いであろうIEで実用的な速度がでないのでは問題だ。

Firebugのプロファイリングで一番ボトルネックになっているメソッドを見つけ出し、外部からオーバーライドして(こういうときJSってステキと思う。継承なしでオーバーライドできちゃうんだもん)、40~50%くらいの改善が見られた(それでもサクサクとはいかない)のだが、今度はなぜかOperaで動作が不安定になった。むぅ。

結局スクラッチすることに

少し悩んだ挙句、多少他の部分の進捗状況が良いことを口実に自分でスクラッチすることにした。午後から6時間ほどかかり切りでなんとか使えるものができたが、いやーUIライブラリは面倒くさいわ。今回の業務向けと割り切って、テーマだなんだと見た目の部分はあまり手をかけてないがそれでもこんだけ時間がかかる。いや、多分dara-jの手が遅いだけなんだけども。このくらい2~3時間でさくっとできるスキルがほしい。

文句たれてるだけじゃ生産性がないので、ちょっとしたコネタをば。(つか、常識?)

今回の日付選択コントロールみたいにポップアップして何かを選択するようなインターフェイスの場合、関係ない部分をクリックしたら閉じるようにするのにどうやってイベント拾ってきたらいいのか良くわからなかったんだけど、documentってonclickがあるのね。

ポップアップさせた要素(と関連する要素)のonclickだけイベントバブリングをキャンセルすれば、それ以外の、ウィンドウ中のあらゆる場所のクリックイベントをdocument.onclickに集約できるので、ポップアップさせた要素を閉じさせるイベントをadd/attachすればよかったのか。思ったより簡単。

以下、サンプル。ここではポップアップトリガのボタンとポップアップ要素自身のクリックイベントの場合のみEvent.stop()をしている。

<html>
<head>
<style type="text/css">
div#panel {
	width : 100px;
	height: 100px;
	border: solid 1px dimgray;
	color : dimgray;
}
</style>
<!-- prototype.jsが必要ね -->
<script type="text/javascript" src="prototype.js"></script>
</head>
<body>
<button id="test">test</button>
<hr>
<div id="panel" style="display: none">
</div>
</body>
<script>
(function() {
	// button#test
	Event.observe( $("test"), "click", function(evt) {
		Element.show( $("panel") );
		Event.stop( evt );
	} );
	
	// div#panel
	Event.observe( $("panel"), "click", function(evt) {
		Event.stop( evt );
	} );
	
	// document
	Event.observe( document, "click", function(evt) {
		Element.hide( $("panel") );
	} );
})();
</script>
</html>

まとめ

  • spinelzのDatePickerは重たいぞ。もっとがんばれ。
  • document.onclickってイベント拾えるんだ。
  • Firebugのプロファイリング機能、めっさ便利(いまさら)
  • もっとブラウザ向けのスクリプト書いとけ > 俺