DOM ロード関連のイベントの発火順番を調べるテストを書きました。
TEST : DOM Load Event
上記テストでは、いくつかの手法を一つのファイルにまとめています。
IE で DOM ロードを検知するには、script defer を使うのが優れた方法だとおもいますが、document.write を使いたくない場合には、doScroll の動作検知を利用したり、setTimeoutを使わない cached htc を利用したりと多様なやり方があることが分かりました。
2007-12-12
TEST : DOM Load Event
IE/DOMLoad : cached htc
document.write を使わず、外部ファイルのダウンロードをしない、IE用の DOM ロードイベントを取得する方法を考えました。
DHTMLの ondocumentready イベントを利用します。
そのためには、DOM要素に behavior 属性で htc ファイルへの参照設定が必要となります。そこで、外部ファイルのダウンロードを不要とするために、ブラウザのキャッシュ機能の利用を考えて、javascriptファイルの拡張子を htc とします。そしてブラウザ判定でIEならば、拡張子が htc である javascript ファイル自身のURLをbehavior属性に渡します。これによりブラウザのキャッシュが使用され、新たな通信が不要となることが期待されます。
HTML :
<script src="http:// ... /onready.htc" type="text/javascript" charset="utf-8"></script>javascript :
onready(function(){ alert('how are you?'); }); onready(function(){ alert('fine! and you?'); }); onready(function(){ alert('me, too!'); });
onready.htc :
/* <?xml version="1.0" encoding="utf-8" standalone="yes"?> <public:component xmlns="http://www.w3.org/1999/xhtml" xmlns:public="urn:HTMLComponent"> <public:attach event="ondocumentready" onevent="element.init();" /> <!-- onready.htc : Cross-Browser DOMContentLoaded Event Listener with Cached HTC script hack for IE Copyright (c) 2007 latchet (http://juce6ox.blogspot.com/) Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) */ var onready = (function(){ var id, ua = navigator.userAgent.toLowerCase(), fs = [], i = 0, done = false, init = function(){ var f, j = 0; if(done) return; done = true; /*@cc_on document.documentElement.init = null; if(id != undefined) document.documentElement.removeBehavior(id); window.detachEvent('onload', init); document.onreadystatechange = null; /*@if(false)@*/ document.removeEventListener('DOMContentLoaded', init, false); window.removeEventListener('load', init, false); /*@end@*/ window.onload = null; while(f = fs[j++]) f(); }, /*@cc_on url = (function(e, ok){ while(e = e.lastChild) if(e.nodeName.toLowerCase() == 'script' && ok.test(e.src)) return e.src; })(document, /^[^?#]+\.htc$/), @*/ set = function(){ var ok; /*@cc_on document.documentElement.init = init; if(url) id = document.documentElement.addBehavior(url); window.attachEvent('onload', init); document.onreadystatechange = function(){ if(document.readyState == 'complete') init(); }; /*@if(false)@*/ if(/mozilla/.test(ua) && !/compatible|webkit/.test(ua) || /opera/.test(ua)) document.addEventListener('DOMContentLoaded', init, false); else if(/webkit|khtml/.test(ua) && (ok = /loaded|complete/)) (function(){ return ok.test(document.readyState) ? init() : setTimeout(arguments.callee, 0); })(); window.addEventListener('load', init, false); /*@end@*/ var old = window.onload; window.onload = (typeof old == 'function') ? function(){old();init();} : init; }; return function(f){ if(typeof f != 'function') return; if(done) return f(); if(!i) set(); fs[i++] = f; }; })(); //--></public:component>
参考資料
- window.onload - An Alternative Solution
- XML版HTCを書くための永久保存版テンプレートとbehaviorを使った時の実行順序、イベントなど - Ci.nsIZIGOROu - Mozilla 拡張機能勉強会
- TAKESAKO @ Yet another Cybozu Labs: Operaでも非同期リクエストが並列処理できる img-JSONP
- Shibuyajs24 JavaScript.GIF x LiveConnect » SlideShare
通常の外部 HTC ファイルをロードして ondocumentready イベントを設定する方法について。
HTCファイルのXML表現をはじめとする丁寧で詳細なまとめ。
ブラウザのキャッシュ機能を利用するアイデア。
拡張子が js でないファイルを script 要素でロードし javascript とみなすアイデア。
IE/DOMLoad : behavior expression
IEの独自実装による document.write を使わない DOMロード の検知方法を考えました。
CSSのbehaviorとexpressionを使います。
behaviorは画面描画のたびに頻繁に評価されます。
expressionを用いると、javascriptを記述できます。
上記を利用することで、Diego Perini氏による doScroll の動作チェックコードの頻度をあげるというアイデアです。
アイデアの中心となるコードは次のようなものです。
document.documentElement.style.setExpression('behavior', 'document.documentElement.polling()');
これを利用したDOMロードイベントのライブラリを公開します。
利用方法は、関数 onready の引数に、DOMロード後に実行したい関数を渡すだけです。
HTML :
<script src="http:// ... /onready.js" type="text/javascript" charset="utf-8"></script>javascript :
onready(function(){ alert('how are you?'); }); onready(function(){ alert('fine! and you?'); }); onready(function(){ alert('me, too!'); });
onready.js :
/* onready.js : Cross-Browser DOMContentLoaded Event Listener Copyright (c) 2007 latchet (http://juce6ox.blogspot.com/) Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) */ var onready = (function(){ var id, ua = navigator.userAgent.toLowerCase(), fs = [], i = 0, done = false, /*@cc_on oldExp, @*/ init = function(){ var f, j = 0; if(done) return; done = true; /*@cc_on if(oldExp) document.documentElement.style.setExpression('behavior', oldExp); else document.documentElement.style.removeExpression('behavior'); window.detachEvent('onload', init); document.onreadystatechange = null; /*@if(false)@*/ document.removeEventListener('DOMContentLoaded', init, false); window.removeEventListener('load', init, false); /*@end@*/ window.onload = null; while(f = fs[j++]) f(); }, set = function(){ var ok; /*@cc_on oldExp = document.documentElement.style.getExpression('behavior'); document.documentElement.polling = function(){ try{ document.documentElement.doScroll('left'); }catch(e){ return setTimeout(document.documentElement.polling, 0); }; document.documentElement.polling = null; init(); }; document.documentElement.style.setExpression('behavior', 'document.documentElement.polling()'); window.attachEvent('onload', init); document.onreadystatechange = function(){ if(document.readyState == 'complete') init(); }; /*@if(false)@*/ if(/mozilla/.test(ua) && !/compatible|webkit/.test(ua) || /opera/.test(ua)) document.addEventListener('DOMContentLoaded', init, false); else if(/webkit|khtml/.test(ua) && (ok = /loaded|complete/)) (function(){ return ok.test(document.readyState) ? init() : setTimeout(arguments.callee, 0); })(); window.addEventListener('load', init, false); /*@end@*/ var old = window.onload window.onload = (typeof old == 'function') ? function(){old();init();} : init; }; return function(f){ if(typeof f != 'function') return; if(done) return f(); if(!i) set(); fs[i++] = f; }; })();
参考資料
- Diego Perini : IEContentLoaded - An alternative for DOMContenloaded on Internet Explorer
- msdn : behavior Property
- msdn : setExpression Method
- Jesse Skinner : addDOMLoadEvent - The Future of the Web
このアイデアを思いついてコードを書き終えたころに、細かい違いはありますが、 同じようにbehaviorとexpressionを使うアイデアを、doScrollハックの考案者であるDiego Perini氏がjQueryのメーリングリストに投稿していたことを検索の結果、知りました(笑)
Nabble - Re: document.ready firing too early within iframe
その時は正直、すこしがっくりしましたが、気付かなかったからこそ取り組めた面もあるのかもしれません。コードを整えつつ、その後、新しい別の方法が無いか再び試行錯誤し、(自分にとって)新しいアプローチを見つけました。document.writeは使わず、IEのDHTMLのondocumentreadyイベントを利用しつつ、新たな通信をしなくてもすむとおもわれる方法です。
IE/DOMLoad : cached htc
window.onload Problem
javascript の window.onload イベントはページの解析、画像などのファイル読込が全て完了してから発生するようです。
そのため、ロードイベント前の文書の解析後に迅速にページのDOM要素に対してjavascriptで操作を行ないたいという要求がおこるのは、ユーザーの利便性を考慮すると当然といえそうです。
Ajaxという言葉が注目され、Googleによるjavascriptを活用したアプリケーションが登場し、
回線速度の向上、インフラ整備、ブログ作成者・閲覧者の増加、ウェブAPIの公開、
マッシュアップサイトが数多く登場しています。
そのような背景の中でjavascriptによる DOM ロードイベント検知の方法もいろいろ探究されてきたようです。今までの流れを書き出してみました。
- 1993/04/30 : Tim Berners-Lee , CERN - World Wide Web 無料開放
- 1995 : Brendan Eich - JavaScript誕生
- 1998/10/01 : W3C - DOM1仕様勧告
- 1999/03/18 : Microsoft - IE 5 リリース
- 2004/04/01 : Google - GMail 告知
- 2005/02/08 : Google - Google Maps 告知
- 2005/02/18 : Jesse James Garrett - Ajax 提唱
- 2005/09/24 : Dean Edwards - DOMロード問題
- DOMContentLoaded ( Mozilla/Firefox )
- script defer ( IE : 外部 javascript ファイル読込あり )
- 2005/09/25 : Dean Edwards - DOMロード問題
- ondocumentready ( IE : 外部 HTC ファイル読込あり )
- 2005/09/30 : Tim O'Reilly - Web 2.0 提唱
- 2006/04/20 : livedoor - livedoor Reader リリース
- 2006/06/08 : Matthias Miller - DOMロード問題
- script defer ( IE : 外部 javascript ファイル読込なし )
- 2006/06/15 : John Resig - DOMロード問題
- document.readyState ( Safari )
- 2006/06/20 : Opera 9 リリース
- DOMContentLoaded ( Opera )
- 2007/06/28 : Diego Perini - DOMロード問題
-
The window.onload Problem - Solved!
-
window.onload - An Alternative Solution
-
The window.onload Problem Revisited « Out of Hanwell
-
window.onload (again)
-
Your Web, Your Choice: Opera 9、これまでにない操作感を
枯れた技術が活用され、以前は想定されなかった問題が解決されていく様子に見えます。
HTML5草案では、DOMContentLoadedイベント(FirefoxとOperaは実装済)が提案されているそうです。
将来、SafariとIEにも採用されたら多くの人に喜ばれそう。