IEの腐ったEventを直す方法

こんにちは。ひろきのだいちです。

今日はブラウザ界のやんちゃボーイ Internet ExplorerのEvent実行順序を調整する方法を考えたんでつらつら書きます。

ただ、すべてのDOMイベントを他のブラウザと互換を取るのはむずかしいので、prototype.js(1.6 or later)のdom:loadedイベントとwindow のloadイベントの実行順序を整列させます。

その後、単一のイベント間の実行順序がattach順にならないというIEのバグをDOM準拠のAPIを追加することで修正します。

JAVASCRIPT:
  1. 10..times(function(i){
  2.     Element.observe(window,'load',function(){
  3.         alert('window loaded'+i);
  4.     });
  5.  
  6. })
  7. 10..times(function(i){
  8.     document.observe('dom:loaded',function(){
  9.         alert('dom loaded'+i);
  10.     });
  11.  
  12. })

dom:loadedとはDOM Treeのパースが終わってjsからさわっても大丈夫だよ!
というイベントです。(細かい定義はしらない。)

またwindowオブジェクトのloadイベントはHTML内の全部のコンテンツのロードがおわったよ!
というイベントです。

これをIE以外のブラウザで実行すると

dom: loaded:0123456789
window load:0123456789

の順番で実行されます。

ところがIEで実行すると・・・・

window load:5432689710
dom: loaded:1324689750

シュールすぎる・・・・TT

さて、まずどこから手をつけましょうか。

dom:loadedイベントとwindow onloadイベントの実行順序が逆っていうのから対処しましょうか。

実はコレ、
HTML中に画像が1つでもあれば、大丈夫で、画像の存在するページで実行すると

dom: loaded:1324689750
window load:5432689710

上記のようにdom loadedイベントが先に発生します。

ところが、これらの画像が全部キャッシュされていた場合・・・・


window load:5432689710
dom: loaded:1324689750

またもや、window loadが先に実行されます。

そこで、

JAVASCRIPT:
  1. if(Prototype.Browser.IE)
  2.     (function(){
  3.         var flag =true;
  4.         Event.observe(window,'load',function(){
  5.             if(flag){
  6.                 document.fire('dom:loaded');
  7.                 document.stopObserving('dom:loaded');
  8.             }
  9.         });
  10.         document.observe('dom:loaded',function(evt){
  11.             flag =false;
  12.         });
  13.     })();

のようにIEの場合のみ、window loadイベントが先に発生し場合に
dom:loadedイベントを発生させて、その後のおこるdom:loadedイベントを全部キャンセルしてみましょう。

すると

dom: loaded:1324689750
window load:5432689710

画像アリ、ナシともにdom:loadedが先に発生しているようです。

次に実行順序です。

問題はEvent.observe内で行っているattachEventが、まともじゃないってことなので
これを是正しましょう。

これは結構根の深い問題なので、
attachEventを極力使いたくありません。

なのでIEにaddEventListener/removeEventListenerを実装してしまいましょう。

そしてその中で1つだけattachEventをもちいて、

JAVASCRIPT:
  1. element.attachEvent('on'+eventName,function(evt){
  2.      cache.each(function(e){e(evt)});
  3. })

のようにキャッシュに積んでおいてあるEvent Handlerたちを実行すれば、
attachEventは一度しか使わないのできれいに整列されます。

そうすると・・・・

dom: loaded:0123456789
window load:0123456789

このようにIEでも実行順序を間違えることなくきれいに整列させることができました!
以下コードです。

JAVASCRIPT:
  1. if (!Prototype) throw ('Event.Wrapper require prototype.js');
  2. if (parseInt(Prototype.Version)> 1.6) throw ('Event.Wrapper require prototype.js v1.6 or later');
  3.  
  4. if(Prototype.Browser.IE)(function() {
  5.  
  6.     var eventCache ={};
  7.     var wrapperCache = {};
  8.     function getEventCache(elementID,eventName){
  9.         if(!eventCache[elementID])eventCache[elementID]={};
  10.         if(!eventCache[elementID][eventName])eventCache[elementID][eventName]=[];
  11.         return eventCache[elementID][eventName];
  12.     }
  13.     function createFixedOrderWrapper(elementID,eventName){
  14.         var wrapper= function(event){
  15.             getEventCache(elementID,eventName).each(function(func){
  16.                 func(event);
  17.             });
  18.         };
  19.         if(!wrapperCache[elementID])wrapperCache[elementID]= {};
  20.         wrapperCache[elementID][eventName] = wrapper;
  21.         return wrapper;
  22.     }
  23.     function getEventId(element) {
  24.         return element._prototypeEventID || element._eventID;
  25.     }
  26.     function addEventListenerIE(element,eventName,func,capture){
  27.         var id = getEventId(element);
  28.         var length =getEventCache(id,eventName).push(func);
  29.         if(length == 1){
  30.             element.attachEvent('on'+eventName,createFixedOrderWrapper(id,eventName));
  31.         }
  32.     }
  33.     function removeEventListenerIE(element,eventName,func,capture){
  34.         var id = getEventId(element);
  35.         var cache =getEventCache(id,eventName);
  36.         if(cache.length>0){
  37.             eventCache[id][eventName]=cache.without(func);
  38.             if(eventCache[id][eventName].length == 0){
  39.                 element.detachEvent('on'+eventName,wrapperCache[id][eventName]);
  40.             }
  41.         }
  42.     }
  43.    
  44.     Element.addMethods({
  45.         addEventListener:addEventListenerIE,
  46.         removeEventListener:removeEventListenerIE
  47.     });
  48.     Object.extend(window, {
  49.         addEventListener: addEventListenerIE.methodize(),
  50.         removeEventListener: removeEventListenerIE.methodize()
  51.     });
  52.     Object.extend(document, {
  53.         addEventListener: addEventListenerIE.methodize(),
  54.         removeEventListener: removeEventListenerIE.methodize()
  55.     });
  56.     (function(){
  57.         var flag =true;
  58.         Event.observe(window,'load',function(){
  59.             if(flag){
  60.                 document.fire('dom:loaded');
  61.                 document.stopObserving('dom:loaded');
  62.             }
  63.         });
  64.         document.observe('dom:loaded',function(evt){
  65.             flag =false;
  66.         });
  67.     })();
  68. })();

« Javascript でHTML::Template – クロスブラウザチックなonbeforeunload。 »

No Comments »

No comments yet.

Leave a comment

 

WP-Design: Vlad -- Powered by WordPress -- XHTML 1.0