次の方法で共有


HTML5

ブラウザーと機能検出

Sascha P. Corti

 

Web サイトを構築していると、今だけ見栄えがよいのではなく、今後もその見栄えを保ちたいと考えます。つまり、Web サイトを、現バージョンのブラウザーだけでなく、今後リリースされる新しいバージョンのブラウザーでも機能させる必要があります。今回の記事では、この目標を達成するのに役立つヒントとベスト プラクティスをいくつか紹介します。

簡単な歴史

現在は、どの Web ブラウザーも、「最新の仕様に沿って Web ページを最適にレンダリングする」という共通の目標に基づいて構築されています。

しかし、昔からそうだったわけではありません。これまでは、ブラウザーの競争が激化するにつれて、ほとんどのブラウザー ベンダーは、たとえ標準になっていなくても需要が高い機能を実装してきました。当然、それは、各ブラウザー独自の方法で行われてきました。たとえば、CSS での透明度の設定の違いを次に示します。

バージョン 8 以前の Internet Explorer では、以下の CSS を認識します。

.transparent {      /* Internet Explorer < 9 */
    width: 100%;
    filter:alpha(opacity=50);
}

これに対し、Firefox には、次のような固有の属性があります。

.transparent {
    /* Firefox < 0.9 */
    -moz-opacity:0.5;
}

Safari も同様です。

.transparent {
    /* Safari < 2 */
    -khtml-opacity: 0.5;
}

ところが、CSS3 では、要素の透明度を設定する方法が次のように統一されます。

.transparent {
    /* IE >= 9, Firefox >= 0.9, Safari >= 2, Chrome, Opera >= 9 */
    opacity: 0.5;
}

標準になっていない機能をサポートするために労力を費やすことはブラウザーにとっては良いことかもしれませんが、Web 開発者にとっては、ページに機能を追加する際に、その機能のさまざまな実装方法をすべて考慮しなくてはならないため、必要以上に苦労するはめになります。

一貫性のあるマークアップ

すべてのブラウザーで Web ページが最適にレンダリングされるようにする一番の方法は、あらゆるブラウザーの最新バージョンで必ずサポートされているマークアップに注目することです。つい最近まで、これに該当するマークアップは、非常に機能が限られている HTML 4.01 で、これは 10 年間も使われていました。

現在、どのブラウザーも、機能豊富な HTML 5 に集約されようとしていますが、一般用語にまとめられている新しい仕様の多く (HTML5 マークアップ、DOM レベル 2、3 などのその API、CSS3、SVG、EcmaScript 262 など) は、現在も開発中のため、変更される可能性があります。ブラウザー ベンダーは、HTML5 の新機能へのサポートの追加を続けていますが、そのペースはベンダーによって大きく異なります。

HTML5 の新しい仕様は、開発初期のため変更される可能性があり、セキュリティに関する問題もありますが、こうした新しい仕様を非常に早く採用しているのが、Firefox と Opera です。開発者にとって、新機能を試すことは興味深いことです。しかし、仕様とその実装の間に存在する大きな違いにより、ブラウザーのバージョンが変わると Web ページが適切に表示されなくなることがあります。これは、ユーザーと開発者のどちらにとっても不快な経験です。一例を挙げると、セキュリティ上の理由により、Firefox 4 はベータ 7 および 8 で Web Sockets を無効にしています。

HTML5 の新たな標準を非常に早く採用したブラウザーの 1 つである Chrome は、普及している h.264 ビデオ コーデックを HTML5 <video> 要素でサポートすることを取りやめ、使用料がかからない WebM 標準をサポートすることを発表 (英語) して、HTML5 コミュニティを驚かせました。これは、h.264 ライセンスに現在費用をかけている開発者にとっては良いことですが、開発者は、できる限り多くのブラウザーをサポートするために、さらに多くの選択肢を探さなくてはなりません。

マイクロソフトは、他のベンダーよりも標準の実装が遅れてはいますが、テスト スイートを作り上げるために W3C (英語) と密接に連携し、仕様のあいまいさを最小限に抑えて、HTML5 がブラウザーでレンダリングされる方法をベンダーが均質化できるようにするための技術基盤を構築しています。この分野の最新動向については、IE 試用版 (英語) で、Internet Explorer 10 プラットフォームのプレビューをご覧ください。また、マイクロソフトが、W3C などの Web 標準化団体からの、まだ安定段階ではない初期仕様のプロトタイプを作成している HTML5 ラボ (英語) も参照してください。Internet Explorer 9 が、現在のさまざまな HTML5 仕様をどのようにサポートしているかについては、「Internet Explorer 9 開発者ガイド」の「標準規格サポートによる相互運用性の強化」を参照してください。

HTML5 の新しい標準はこれから目指すべき目標ですが、インターネット ユーザーの大半が Web ブラウザーの最新バージョンをまだ使用していないため、適切なマークアップを使用することがかつてないほど重要になっています。

ブラウザーの検出

ブラウザーの違いに対処するアプローチの 1 つは、ブラウザーの検出を使用することです。最も一般的なのは、次のように、JavaScript を使用してユーザー エージェントのヘッダーをクエリする方法です。

<script type="text/javascript">
  if ( navigator.userAgent.indexOf("MSIE")>0 )
  {
    // Run custom code for Internet Explorer.
  }
</script>

このアプローチには問題点が 2 つあります。まず、ブラウザーの検出では、1 回のチェックで、ブラウザーがサポートする機能についての複数の推定をまとめて行うことになります。1 つでも誤った推定を行うと、Web サイトは機能しません。そのため、開発者として、特定のブラウザーの各バージョンがサポートする機能について明確に把握しておかなければなりません。

2 つ目に、このブラウザー チェックでは、ブラウザーのバージョンが考慮されないため、新しいバージョンに対応できません。ブラウザーの検出を使ってサイトに追加した回避策が、現バージョンのブラウザーでは機能しても、新しいバージョンではその回避策を必要としなくなるかもしれません。さらに、新しいバーションではその機能自体のサポートが完全に中止されている可能性さえあります。

したがって、ブラウザーの検出を使用する際はバージョンを考慮し、レガシ ブラウザーを検出する場合にのみ使用します (図 1 参照)。

図 1 レガシ ブラウザーの検出

<script type="text/javascript">
  functiongetInternetExplorerVersion()
  // Returns the version of Internet Explorer or a -1 for other browsers.
  {
    var rv = -1;
    if(navigator.appName == 'Microsoft Internet Explorer')
    {
      var ua = navigator.userAgent;
      varre  = newRegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
      if(re.exec(ua) != null)
      rv = parseFloat( RegExp.$1 );
    }
    return rv;
  }
  functiononLoad()
  {
    var version = GetInternetExplorerVersion()
    if (version <= 7 && version > -1)
    {
      // Code to run in Internet Explorer 7 or earlier.
    }
  }
</script>

詳細については、MSDN ライブラリの「Internet Explorer をより効率的に検出する」(英語) を参照してください。さまざまなブラウザーとその正確なバージョンを検出するために navigator オブジェクトと正規表現を使用する方法についての包括的な記事は、「JavaScript のチュートリアル」(英語) からご覧いただけます。

Internet Explorer には、バージョン 5 から、条件付きコメントを使用してブラウザーを検出する独自の方法があります。この構文は、標準の HTML コメントを拡張します。CSS と共に条件付きコメントを使用すると、他のブラウザーでは無視される、Internet Explorer 固有の CSS ルールを実装できます。次の例では、Internet Explorer 7 以前のバージョンが検出された場合のみ ie7.css が読み込まれます。

<!--[if lte IE 7]>
  <style TYPE="text/css">
    @import url(ie7.css);
  </style>
<![endif]-->

条件付きコメントの使用法の詳細については、MSDN ライブラリの「条件付きコメントについて」(英語) を参照してください。

ですが、ブラウザーの検出に存在する問題点や制限事項をすべて考慮すると、別の選択肢を取る方が適切です。

機能検出

Web ブラウザーの違いに対処するより適切なアプローチが、機能検出です。知っている機能を使用する前に、ブラウザーごとに実装が異なる可能性があることを考え、特定のオブジェクト、メソッド、プロパティ、または動作が使用可能かどうかを知るための簡単なテストを実行します。多くの場合、対象となる機能の新しいインスタンスを作成することによって、このテストを実行できます。インスタンスを作成するときに null 以外の値が返されれば、実行中のブラウザーはその機能を認識します。null が返される場合は、回避策を使用できるかどうか、または機能に独自の古い実装があるかどうかをテストします。

ブラウザーの検出と機能検出の比較

図 2図 3、および図 4 で、検出の 2 つのアプローチがさまざまな状況で機能するようすを見てみましょう。

Possible Code Paths Through the Test Site
図 2 テスト サイトで可能性のあるコード パス

Results with Well-Known Browser Configurations
図 3 既知のブラウザー構成による結果

ブラウザーの構成が既知の場合はどちらのアプローチも機能しますが、ブラウザーの検出では、機能 A と機能 B がどちらもブラウザーによってサポートされると決めてしまっていますが、機能検出では機能を 1 つ 1 つテストします。

Unknown Browser Configuration
図 4 未知のブラウザー構成

興味深いのは、ブラウザーの構成が未知の場合です。機能検出はこのような場面に適切に対処し、機能 A はブラウザーで表示できても、機能 B にはフォールバック コードが必要であることを認識します。一方、ブラウザーの検出では、クエリしたブラウザーとバージョンの組み合わせが未知で一致するものがないため、ブラウザーの名前に基づいてパスを選択するか、既定のパスを選択します。実際には、ブラウザーの構成が未知の場合に適切な表示を行うのに必要なコードがページにすべて含まれていても、すべての適切なコード セグメントにつながっているコード パスがないため、どちらにしても、この例ではページは適切にレンダリングされません。

機能検出の例

機能検出を使用する際、2 つの非常に重要な推奨事項に留意する必要があります。

  • 常に標準を最初にテストする: 多くの場合、ブラウザーは、以前の回避策と新しい標準を両方サポートします。
  • 1 回のチェックでは常に関連する機能のみを対象とする: これにより、ブラウザー機能の推定を事実上最小限に抑えます。

では、機能検出の例をいくつか見てみましょう。

次のスクリプトは、2 つのコード パスを作成します。まず、ブラウザーが window.addEventListener をサポートしているかどうかをチェックし、サポートしていなかったら、以前の機能の window.attachEvent が使用可能かどうかを調べます。

<script type="text/javascript">
  if(window.addEventListener) {
    // Browser supports "addEventListener"
    window.addEventListener("load", myFunction, false);
  } else if(window.attachEvent) {
    // Browser supports "attachEvent"
    window.attachEvent("onload", myFunction);
  }
</script>

機能検出を一連の関数にカプセル化して、コードのどこにでも使用可能にすることも適切なアプローチです。次に、ブラウザーが HTML5 の <canvas> 要素をサポートするかどうかを検出し、サポートしている場合に canvas.getContext(‘2d’) メソッドが機能することも確認するためのベスト プラクティスを示します。この関数は true か false を返すだけなので、簡単に再利用できます。

<script type="text/javascript">
  functionisCanvasSupported()
  {
    var elem = document.createElement('canvas'); 
    return!!(elem.getContext && elem.getContext('2d');
  }
</script>

機能検出は、新しく要素やオブジェクトを作成するときに、常に使用するようにしてください。これは、要素またはオブジェクトが作成された後、ページの別のスクリプトによって変更される可能性をなくし、ランダムな結果や不安定な結果が生じないようにするためです。

また、機能検出は、HTML5 の <video>、<audio>、<canvas> などのいくつかの HTML 要素については、"フォールバック" の形式で直接機能します。ブラウザーは、最初にサポート対象のサブ要素を上から表示して、その下の要素を非表示にします。

最も簡単な形式は次のようになります。

<video src="video.mp4">
    Your browser doesn't support videos natively.
</video>

<video> 要素をサポートするブラウザーは "video.mp4" というビデオを表示しますが、サポートしないブラウザーは指定されたテキストをフォールバックとして表示します。ただし、フォールバックは、次のように、video タグのさまざまなビデオ形式に対しても機能します。

<video>
    <source src="video.mp4" type="video/mp4" />
    <source src="video.webm" type="video/webm" />
    Your browser doen't suppport videos natively.
</video>

この場合、HTML5 の <video> をサポートするブラウザーは、まず mp4 ビデオを読み込もうとします。この形式をサポートしない場合は、WebM でエンコードされたビデオをフォールバックとして読み込もうとします。その形式もサポートしていなければ、またはまったく <video> をサポートしていなければ、テキストをフォールバックとして表示します。

もちろん、ブラウザーが HTML5 の <video> をまったくサポートしない場合のために、テキストではなくプラグインのビデオ プレーヤーにフォールバックする方が合理的です。次の例では、Silverlight のビデオ プレーヤーが使用されます。

<video>
    <source src="video.mp4" type='video/mp4' />
    <source src="video.webm" type='video/webm' />
    <object type="application/x-silverlight-2">
        <param name="source" value="http://url/player.xap">
        <param name="initParams" value="m=http://url/video.mp4">
    </object>
    Download the video <a href="video.mp4">here</a>.
</video>

非常によく似たロジックが、CSS でも同様に機能します。CSS では、認識されないプロパティは単に無視されます。このため、下記の "border-radius" のような、ベンダーによってプレフィックスが付けられているプロパティを実験のために複数追加する場合は、コードにすべてのバリエーションを含めるだけでかまいません。これは少々不明確に感じられるかもしれませんが、上記のような場合に使いやすく、作業を実行することができます。実験のために含めるベンダー固有のプレフィックスを最初に置いて、最後に標準のマークアップを置くのがベスト プラクティスです。

<style type="text/css">
    .target
    {
        -moz-border-radius: 20px;
        -webkit-border-radius: 20px;
        border-radius: 20px;
    }
</style>

機能検出が非常に優れている点は、ページを作成する際には考えていなかったブラウザーでも、新しいバージョンのブラウザーでも機能することです。これは、ブラウザーがサポートする機能の推定に依存しないためです。

機能検出の開発とテスト

Internet Explorer 9 の F12 開発者ツールは、さまざまなブラウザーで、機能検出を開発およびテストするのに最適です。このツールを使用すると、1 つ 1 つ手順を追いながらスクリプトをデバッグし、ブラウザーのユーザー エージェントの文字列を変更したり、Internet Explorer に前のバージョンのレンダリング エンジンを使用するよう指示したりできます。図 5図 6図 7、および図 8 で、このツールの使い方の例をいくつか示します。

Using Breakpoints while Debugging JavaScript in Internet Explorer 9
図 5 Internet Explorer 9 で、JavaScript のデバッグ中にブレークポイントを使用する

Running in “Document Mode: IE9 standards,” the bBrowser Uses the Modern addEventListener Method
図 6 ブラウザーを [ドキュメント モード: IE7 標準] で実行し、最新の addEventListener メソッドを使用する

Running in “Document Mode: IE7 standards,” the Debugger Falls Back to the Legacy attachEvent Method
図 7 デバッガーを [ドキュメント モード: IE7 標準] で実行し、以前の attachEvent メソッドにフォールバックする

You Can Change the Internet Explorer User Agent String on the Fly and Even Add Your Own Strings, Including Those for Mobile Browsers
図 8 Internet Explorer のユーザー エージェントの文字列をその場で変更でき、また、モバイル ブラウザーの文字列など、独自の文字列を追加することもできる

大規模プロジェクトでの機能検出の管理

複雑な Web プロジェクトを作成するときは、すべての機能検出コードの作成と管理を行うのは面倒です。さいわい、JavaScript ライブラリの ModernizrjQuery が役立ちます。

Modernizr には、非常に簡単にコードで使用できる、HTML5 と CSS3 のほとんどの機能の検出が組み込まれています。これは非常に幅広く採用され、絶えず拡張されています。Modernizr と jQuery は、どちらも ASP.NET MVC のツールに付属しています。

Web フォントを表示するブラウザーの機能の検出に、Modernizr を使用しない図 9 のコードと、Modernizr を使用する図 10 のコードを見比べてください。

図 9 Modernizr を使用しないコード

function(){
  var
    sheet, bool,
    head = docHead || docElement,
    style = document.createElement("style"),
    impl = document.implementation || { hasFeature: function()
      { return false; } };
    style.type = 'text/css';
    head.insertBefore(style, head.firstChild);
    sheet = style.sheet || style.styleSheet;
    var supportAtRule = impl.hasFeature('CSS2', '') ?
      function(rule) {
        if (!(sheet && rule)) return false;
          var result = false;
          try {
            sheet.insertRule(rule, 0);
            result = (/src/i).test(sheet.cssRules[0].cssText);
            sheet.deleteRule(sheet.cssRules.length - 1);
          } catch(e) { }
          return result;
        } :
        function(rule) {
          if (!(sheet && rule)) return false;
          sheet.cssText = rule;
          return sheet.cssText.length !== 0 &&
            (/src/i).test(sheet.cssText) &&
            sheet.cssText
              .replace(/\r+|\n+/g, '')
              .indexOf(rule.split(' ')[0]) === 0;
        };
    bool = supportAtRule('@font-face { font-family: "font";
    src: url(data:,); }');
    head.removeChild(style);
    return bool;
  };

図 10 Modernizr を使用するコード

<script type="text/javascript" src"modernizr.custom.89997.js"></script>
<script type="text/javascript">
  if(Modernizr.fontface){
    // font-face is supported
  }
</script>

不足する機能のサポートの追加

機能検出を使用しても、テスト対象のブラウザーが必要な機能をサポートしなければ、回避策を考えなければなりません。前述の HTML5 のビデオの例では、フォールバックとして Silverlight を使用することが明らかな解決策でした。ですが、HTML5 の <canvas> などの機能や、<nav>、<section>、<article>、<aside> などの新しいセマンティック タグ、新しい <header> や <footer> などの場合はどうでしょう。

大半の HTML5 機能には、既製の "フォールバック" があり、その数は増え続けています。このようなフォールバックをシムやポリフィルと呼び、回避策を考える労力の軽減に使用できます。シムやポリフィルは、CSS、JavaScript ライブラリ、またはプロジェクトで使用できる Flash や Silverlight のコントロールです。これらを使用すれば、不足している HTML5 機能をブラウザーに追加したり、HTML5 機能をサポートしないブラウザーに HTML5 機能を追加したりすることができます。

基本的な考え方は、開発者は HTML5 API を使って開発でき、スクリプトは存在すべきメソッドやオブジェクトを作成できるというものです。次のバージョンを念頭に置くこのような方法で開発を行えば、ユーザーがアップグレードするたびにコードを変更する必要はなくなり、ユーザーのエクスペリエンスは、適切かつ自然な方向に向かいます。

シムとポリフィルの違いは、シムは機能を模倣するだけで、それぞれに独自の API があるのに対し、ポリフィルは、HTML5 の機能自体と、その正確な API の両方を模倣します。このため、一般には、ポリフィルを使用すれば、独自の API を採用するのに必要な労力を節約できます。

GitHub で公開されている「HTML5 の複数のブラウザーのポリフィル」(英語) では、増え続けている使用可能なシムとポリフィルが一覧になっています。

たとえば Modernizr には、セマンティック タグをサポートするために "HTML5Shim" が含まれていますが、機能がサポートされていないことを Modernizr が検出した場合、他のシムとポリフィルを簡単に読み込むことができます。

動的なサポートの追加

このようなスクリプト ライブラリをすべて追加すると、ページが重くなり、読み込みが遅くなると思っていませんか。

サポートしているこれらのライブラリを多数使用すると、Web サイトにかなりのオーバーヘッドが追加されることは事実なので、本当に必要な場合にだけ、動的にそれらを読み込む方が適しています。シムまたはポリフィルの場合、ブラウザーがネイティブの HTML5 機能をサポートしないことを検出した場合にのみ読み込むのがベスト プラクティスです。

さいわい、yepnope.js (英語) という、まさにこのシナリオをサポートする優れたライブラリがあります。

Yepnope は、JavaScript と CSS の両方を操作して、実行と事前読み込みとを完全に分離する非同期リソース ローダーです。つまり、リソースが実行されるタイミングを完全に制御でき、実行時に順序を変更できます。Yepnope は Modernizr 2 に統合される予定ですが、Yepnope 単独でも使うことができます。その構文を見てみましょう。

<script type="text/javascript" src="yepnope.1.0.2-min.js"></script>
<script type="text/javascript">
    yepnope({
        test : Modernizr.geolocation,
        yep  : 'normal.js',
        nope : ['polyfill.js', 'wrapper.js']
    });
</script>

この Yepnope ページの例は、Modernizr を使用して HTML5 の地理位置情報を使用するブラウザーの能力をテストしています。この能力がサポートされていると、独自のコード (normal.js) が読み込まれます。サポートされていなければ、(polyfill.js と wrapper.js から成る) カスタムのポリフィルが読み込まれます。

特に重要な点を図 11 に示します。

図 11 ブラウザーの検出と機能検出の注意事項

  必須事項 禁止事項
ブラウザーの検出

まとめて使用することを避ける

または

特定のブラウザーとバージョンをテストする

ブラウザー名をテストするだけで、将来のブラウザーについての推定を行う
機能検出 最初に標準をテストする 1 つのテストで、関連していない機能の推定を行う

関連情報:

ブラウザーと機能検出の詳細については、次のリソースを参照してください。

Sascha P. Corti は、マイクロソフトのスイス支社でマイクロソフトのシステム プラットフォームと開発者ツールに特化した開発者エバンジェリストとして活躍しており、スイスの開発者コミュニティに最新のトレンドを紹介しています。最近は、Web テクノロジと Windows Phone 7 プラットフォームに重点を置いています。

彼は、チューリッヒ工科大学でコンピューター サイエンスを、そしてチューリッヒ大学で情報管理を研究しました。

Sascha は、7 年間にわたって、スイスの大手銀行でソフトウェア開発者とアーキテクトを努め、Silicon Graphics やマイクロソフトなどの米国のいくつかのハイテク企業でシステム エンジニアやテクノロジ スペシャリストとして活動していました。

最新の活動については、http://techpreacher.corti.com (英語) や https://blogs.msdn.com/swiss_dpe_team/ (英語) で彼のブログを参照するか、Twitter (@TechPreacher、英語) で彼をフォローしてください。

この記事のレビューに協力してくれた技術スタッフの Justin GarretThomas Lewis に心より感謝いたします。