これでできる! クロスブラウザJavaScript入門

第3回 クロスブラウザの傾向と対策

この記事を読むのに必要な時間:およそ 3.5 分

パターン3:バグ

こちらは一見サポートされているようで実はバグが潜んでいるという,遭遇することは多くないですがもっとも厄介なパターンです。経験的には,古いブラウザや,シェアが少なくて比較的検証されていないブラウザで遭遇しやすい傾向があるように思います。

IEやFirefoxなどはこれまで多くのバグが発見され,同時に対策が公開されているので,バグを正しく把握できれば解決したも同然であることも少なくありません。一方でOpera・Chrome・Safariのバグは検索しても日本語圏での情報が見つからないといったことも珍しくありません。そういった場合,自力でなんとかするしかないかもしれません。

なお,バグはそのブラウザの仕様とみなされていることもあるため,パターン2と区別がつかないこともあります。

JavaScriptではなくCSS関連ですが,IE 6にはfloat指定した要素にmarginを指定すると指定した値の2倍の値が適用されるという比較的有名なバグがあります。float指定と一緒にdisplay:inlineを指定するとこのバグを回避することができます。このとき,IE 6以外ではinlineの指定は余計ですが,float指定された要素はdisplayの指定に関わらず,block要素として扱われるのでinline指定は無視されるので問題ありません。

inline指定によるmarginバグの回避

.floatbox{
width:50px;
margin:20px;
float:left;
display:inline;
}

inline指定なし

left
left
right
right

inline指定あり

left
left
right
right

図1 IE 6での表示

図1 IE 6での表示

パターン4:レガシーコード

ブラウザの実装の中には,標準仕様にはなっていないが各ブラウザがそれなりに足並みを揃えて実装したため,ある程度は共通した実装になっていることがあります。それらの多くはDOM Level 0と呼ばれており,W3CがDOMの仕様を定める前に実装された部分です。現在ではHTML5や関連APIで再定義され,標準化されつつあります。しかし,基本的に互換性のために定義された仕様であり,既存のブラウザ間で微妙な誤差があったり,より高機能なAPIが存在したり,といったことがあるので,より適切な実装を選択するべきです。

レガシーコードによるフォーム操作

<form id="userform" name="userform">
    <input type="text" name="name">
</form>
<script type="text/javascript">
window.onload = function(){
  // name属性で要素にアクセス
  var form = document.userform;//(1)
  //var form = document.getElementById('userform');
  // on + イベント名でイベント処理(2)
  form.onsubmit = function(evt){
    if (!check()) {
      return false;
    }
  };
  function check(){
    // form要素の中にあるnameを参照
    var user = form.name.value;//(3)
    // 省略
  }
};
</script>

(1)ように,formやinputなどの要素に加えて,a要素,img要素などの要素はname属性を指定することができ,そのnameを使って document[name] とすることでその要素を参照することができます。ただし,name属性の値は重複が許されているので,同じnameを持つ要素が複数あった場合は要素自身ではなく要素配列を取得します。つまり,document[name](もしくはform[name])で要素にアクセスする場合は要素自身を取得するか,要素配列を取得するかの2通りがあるため,(1)のようなコードはバグを生みやすくなってします。

そこで,確実に特定の要素を取得できるgetElementByIdを使用することを推奨します(JavaScriptから扱いたい要素にはid属性を指定するようにしましょう⁠⁠。また,同じnameをもつ複数の要素を取得する場合にはdocument.getElementsByNameやgetElementsByTagNameを使用するのがよいでしょう。getElementsByNameはdocumentに固定ですが,getElementsByTagNameはある要素を基点にその子孫にあたる要素だけを取得できるので,取得したい要素に応じて使い分ける必要があります。

(2)のような on+イベント名 によるイベント処理はひとつの要素にひとつのイベントしか設定できないため,うっかり先に定義されていたイベントを消してしまうといったことが起こり得ますし,どちらにしても元のイベントを残しつつ処理を追加するという実装が必要となるため,保守が必要なケースでは推奨できません。すでに何度か登場しているaddEvent関数のような実装をするべきでしょう。もちろん,ちょっとしたコードでは手軽なon+イベントを使用しても問題ありません。なお,on+イベント名の形はHTML4.01,HTML5で定義されています。

また,(3)は「form要素のname属性」と,⁠form要素内のname属性の値がnameであるinput要素」のどちらを参照しているのか見た目で判断できません。実際には,(3)のnameはinput要素であり,form要素のname属性は隠れてしまっています。form要素のname属性を取得したい場合,form.getAttribute('name')とすることでname属性の値を取得できますが,IE 6ではgetAttributeを使ってもinput要素を取得してしまうため,name属性を取得できなくなってしまいます。

パターン5:先行実装

HTML5やCSS 3などの勧告には至っていない仕様を各ブラウザは先行して実装しており,それらを利用することができます。新しい仕様は有用なものばかりですので,積極的に使いたいところです。ただし,非対応ブラウザでどうするのか,勧告までに仕様が変わる可能性があるといったことに注意する必要があります。こちらは少々高度な内容になりがちなので,連載の後半で扱うことになると思います。

パターン6:最適化

最後に,パフォーマンスについても取り上げます。特にIE 6などはブラウザ自体のパフォーマンスが相対的に低く,IE 6が載っているマシンもそれなりに古いものであることを想定する必要があり,そういった環境でもなるべく快適に動作するように最適化を行います。こちらは適宜簡単に言及しつつ,連載の後半で集中的に扱う予定です。

まとめ

今回はクロスブラウザ対応のパターンを整理しました。いくつか具体例をあげましたが,これらは代表的なものを挙げたに過ぎません。実際に実装する上ではこれらの問題がいくつか絡み合ってきます。なお,第1回の入力値チェックはひとつの解答例となっていますので,今回のパターンを踏まえてコードを読んでみると前回と違った発見があるかもしれませんので,是非復習をしてみてください。次回からはIE対策を中心にクロスブラウザ実装の実例を見ていきたいと思います。

著者プロフィール

太田昌吾(おおたしょうご,ハンドルネーム:os0x)

1983年生まれ。JavaScriptをメインに,HTML/CSSにFlashなどのクライアントサイドを得意とするウェブエンジニア。2009年12月より、Google Chrome ExtensionsのAPI Expertとして活動を開始。

URLhttp://d.hatena.ne.jp/os0x/