同じマークアップ: クロスブラウザー コードの記述

本記事は、マイクロソフト本社の IE チームのブログ から記事を抜粋し、翻訳したものです。 

【元記事】Same Markup: Writing Cross-Browser Code (2010/4/15 6:49 AM)

※担当者註 : この記事は 2010 年 4 月の記事ですが、資料性が高いと判断されたためあらためて投稿されました。

先日行われた "MIX10" のセッションでは、私はクロスブラウザーのベスト プラクティスについて説明しました。

セッションの内容は、特定の機能についてではなく、Web 開発者がブラウザー間の違いに対応できるコードを自信を持って記述するには、というものでした。

IE9 ではこうしたブラウザー間の違いへの対応が強化されており、開発者は同じマークアップを使用して異なるブラウザーに対応できるようになっています。同じマークアップの実現によって、適切な機能をサポートすることで、同じ HTML、JavaScript、CSS が "そのまま" 動作するようになります。

しかし、Web 全体で同じマークアップを実現することが一筋縄ではいかない取り組みであることも事実です。もちろん各ブラウザーに適切な機能が搭載されなくてはなりませんが、開発者側もそうした機能を確実に検出して利用することが必要になります。

機能を確実に検出するということに関して、私がこれまで Web で目にしてきた問題やベスト プラクティスの例をご紹介します。ご紹介するパターンを参考にして、皆様にはぜひコードを記述し直し、"同じマークアップ" というコンセプトのメリットを体験していただきたいと思います。今回はこのコンセプトの最も基本的な例に絞って説明いたしますが、今後より詳細で複雑な例をご紹介できると思います。まず、一連のガイドラインから説明していきましょう。

同じマークアップ : 最も重要なガイドライン

  • すべきこと
    • 機能の検出

機能を使用する前に、ブラウザーがその機能をサポートしているかどうかを確かめてください。

    • 動作の検出

既知の問題であることを確認した上で回避策を適用してください。

  • すべきでないこと
    • ブラウザーの検出

ブラウザー検出という操作です。ページの動作をブラウザーの ID (navigator.userAgent など) に応じて切り替えることは避けてください。

    • サポートされていない機能の利用

ある機能の検出だけ行って、そのまま別の機能も使用する、ということをしないでください。

以上のガイドラインが重要である理由は、ハイブリッド コードを使って異なる複数のブラウザーに対応している Web ページがほとんどだからです。こうしたコードには、何をどの条件で実行するかを決定するテストが含まれます。このテストでは、条件分岐によって、ブラウザーごとにどのようにページを表示するかを決定します。一般に以下のようなスクリプトを使用します。

 if( condition ) {
    // メインのコード
} else {
    // 代替コード
}

このようなテストでは多くの場合、”特定の機能が利用できるか” ではなく “どのブラウザーが使われているか” という条件式が用いられますが、これにはブラウザーに応じてコードを切り替えると Web ページの柔軟性が低下する、という問題があり、リリースされたばかりのブラウザーを使うとページ表示が壊れる、という深刻な結果を招く恐れがあります。また、必要のなくなった古い回避策がいつまでも適用されたままになってしまうことも少なくありません。

すべきでないこと : ブラウザーの検出 - イベント登録の例

効果は簡単に検証できます。以下のコードでは、検出されたブラウザーに応じてイベント モデルを切り替えています (このプラクティスは推奨されません)。このコードを IE9 で実行すると、サポートされている addEventListener が使用されていないことを通知します。

 // [TR] 例示のため複数のリスナーを追加
function f1() { document.write("addEventListener was used"); }
function f2() { document.write("attachEvent was used"); }

// 非推奨: ブラウザーを検出
if(navigator.userAgent.indexOf("MSIE") == -1) {
   window.addEventListener("load", f1, false);
} else {
   window.attachEvent("onload", f2);
}

実行する

IE9 の出力 : attachEvent was used (attachEvent が使用されました)

すべきこと : 機能の検出 - イベント登録の例

以下のコードは、機能を検出してイベント モデルを適切に切り替える方法を示しています。IE ではなく、 addEventListener が使えるかどうかをチェックしています。addEventListener が使えないレガシー ブラウザーでは、このコードによって適切なフォールバックが行われますが、より重要なのは、サポートされている場合は必ず addEventListener を使用するという点です。このアプローチを使えば、IE9 と他ブラウザーで同じマークアップを実行できます。

 // [TR] 例示のため複数のリスナーを追加
function f1() { document.write("addEventListener was used"); }
function f2() { document.write("attachEvent was used"); }

// 使用を推奨: 機能を検出
if(window.addEventListener) {
   window.addEventListener("load", f1, false);
} else if(window.attachEvent) {
   window.attachEvent("onload", f2);
}

実行する

IE9 の出力 : addEventListener was used (addEventListener が使用されました)

目的のブラウザー向けのコードを正しく実行するために、さまざまなページやフレームワークで機能検出が採用されるようになっています。機能の検出を行うことで、事前に各ブラウザーにどのような機能があるのかを知らずに、クロスブラウザーのコードを "そのまま" 動作させることが可能になります。機能の検出を最大限に活用している代表的なフレームワークが jQuery です。jQuery.support のドキュメント (英語) では、Webg サイトで jQuery の機能検出を私用する方法が詳しく説明されています。

すべきこと : 動作の検出 - jQuery getElementById の例

jQuery では、シンプルな機能検出に加えて、動作の検出もフルに活用できます。動作の検出では、テストを実行して既知の問題を検出し、回避策が必要かどうかを判断します。jQuery ソース コードの一部を少し修正したものを以下に示します。ここでは、getElementById で取得した要素が "name" 属性を持っているかどうかをチェックしています。これは古い IE のバグで、IE8 では修正されています。

 // name 属性を持つ要素を作成して、挿入
var form = document.createElement("div"),
id = "script" + (new Date).getTime();
form.innerHTML = "<a name='" + id + "'/>";

// ルート要素に挿入、状態をチェックした後、削除
var root = document.documentElement;
root.insertBefore( form, root.firstChild );

// 回避策として getElementById の後に追加チェックを実行
// この結果、他のブラウザーでは処理が遅くなる (このため分岐を実行)
if ( document.getElementById( id ) ) {

   // ... 回避策のコード ...
   // [TR] 例示のために追加
   document.write("getElementById workaround was used");
}

// [TR] 例示のために追加
else document.write("No workaround was used");
root.removeChild( form );

実行する

IE9 の出力 : No workaround was used (回避策は使用されませんでした)

すべきでないこと : サポートされていない機能の利用 - 実際の例

最後に、関係のない機能の利用について触れたいと思います。これは、私たちが IE8 の互換性テストでよく目にした例です。この問題が発生しているサイトでは、ある 1 つの機能を検出した後は、その他のさまざまな機能をサポートの有無をチェックすることなく使用していました。下にコードを示したサイトでは、使用している postMessage と addEventListener という API の内、前者だけしかテストを行っていません (コードは実際の内容とは少し変えてあります)。postMessage は IE8 でサポートされていますが、addEventListener のサポートは IE9 で初めて追加されました。この例をそのまま IE8 で実行するとスクリプト エラーが発生し、残りのスクリプトは実行されません。

 // [TR] IE8 でのスクリプト エラーをトラップする Try-catch ブロックを追加
try { 
      function fn() {}
      if(window.postMessage) {
         window.addEventListener("message", fn, false);

         // [TR] 例示のために追加
          document.write("Message listener registered successfully");
      } else {
         // ... workaround for when postMessage is unavailable ...
         // [TR] 例示のために追加
           document.write("postMessage is not supported, using workaround");
      }

} catch(e) {
   document.write("Message listener registration FAILED");
}

実行する

IE7 の出力 : postMessage is not supported, using workaround (postMessage はサポートされていません。回避策を使用しています)

IE8 の出力 : Message listener registration FAILED (メッセージ リスナーを登録できませんでした)

IE9 の出力 : Message listener registered successfully (メッセージ リスナーが正常に登録されました)

すべきこと : 機能の検出 機能がサポートされているかどうかを個別にチェック

上のコード例を修正したものを以下に示します。このコードでは、postMessage と addEventListener の両方をチェックしてから使用しています。

 function fn() {}

if(window.postMessage) {
   if(window.addEventListener) {
      window.addEventListener("message", fn, false);
   } else if(window.attachEvent) {
      window.attachEvent("onmessage", fn);
   }

   // [TR] 例示のために追加
   document.write("Message listener registered successfully");
} else {
   // ... workaround for when postMessage is unavailable ...
   // [TR] 例示のために追加
   document.write("postMessage is not supported, using workaround");
}

実行する

IE7 の出力 : postMessage is not supported, using workaround (postMessage はサポートされていません。回避策を使用しています)

IE8 の出力 : Message listener registered successfully (メッセージ リスナーが正常に登録されました)

IE9 の出力 : Message listener registered successfully (メッセージ リスナーが正常に登録されました)

さらなる進化に向けて

この記事では、ブラウザーを検出する代わりに機能と動作の検出を実行することのメリットについて説明しましたが、議論はこれでおしまいではなく、むしろ始まったばかりだと考えています。今後のブログ記事でも、Web で確認されたさまざまな問題をより具体的な例を使ってご紹介し、すべてのブラウザーで正常に実行できるコーディング方法を考察していきたいと思います。また、マイクロソフトでは自社 Web サイトの検証も引き続き行い、このようなプラクティスに合わせたサイトの更新も進めてまいります。ご質問やアイデアがございましたらぜひお寄せください。特に、ページ開発で皆様が体験された具体的な問題をお知らせいただけると幸いです。

Tony Ross

プログラム マネージャー