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

第6回JavaScriptとHTMLとDOMの基本#1

こんにちは、太田です。前々回前回とJavaScriptの基礎的な部分を解説しました。今回はJavaScriptからみたHTMLを中心に、DOMについても少しずつ解説しています。

JavaScriptとHTML

FirefoxのアドオンやサーバーサイドJavaScriptなどの例外をのぞいて、多くのJavaScriptはHTML上で実行されるので、HTMLは土台となる重要な要素です。そこでHTMLの基礎的な部分からHTMLとJavaScriptの関係を解説します。

DOCTYPEとレンダリングモード

HTMLといえば最初に書くのはDOCTYPEです。現在使われているDOCTYPEはHTML4.01、XHTML1.0、XHTML1.1といくつかの種類があり、さらにその中でTransitionalやStrictなどの違いや、XHTMLではXML宣言の有無(本来は必須ですが)などバリエーションがあります。DOCTYPEがなかったり、間違ったフォーマットで記述されている場合など、レンダリングエンジンが後方互換モードになります。後方互換モードには色々と問題があるため、標準準拠モードにするために正しいDOCTYPEを記述するように気をつけるとよいでしょう(DOCTYPEについては、DOCTYPE スイッチについてのまとめと一覧表に詳しいまとめがあります⁠⁠。

特に理由がなければ、HTML5のDOCTYPEを使うのがよいでしょう。HTML5はまだ仕様の策定途中ですが、HTML5で定義されているDOCTYPEは互換性の問題を考慮されているのでIE 6などのブラウザでも標準モードとして認識されますし、従来のDOCTYPEよりも大幅にシンプルになっています。例えば、HTML4.01 Transitionalに従ったHTMLは以下のようになります。

HTML4.01のDOCTYPEとサンプルHTML
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="ja">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <meta http-equiv="Content-Script-Type" content="text/javascript">
    <link rel="stylesheet" type="text/css" href="a.css">
    <title>HTML4.01 サンプル</title>
  </head>
  <body>
    <h1>sample html 4.01</h1>
  </body>
</html>

これをHTML5のDOCTYPEなどを使って書き直すと次のようになります。

HTML5のDOCTYPEとサンプルHTML
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <link rel="stylesheet" type="text/css" href="a.css">
    <style>
    body{
      margin:3px;
    }
    </style>
    <title>HTML5 サンプル</title>
  </head>
  <body>
    <h1>sample HTML5</h1>
    <script>
      alert(document.doctype);
    </script>
  </body>
</html>

DOCTYPEのほか、charsetの指定、style, script要素のtype属性の省略(デフォルトがCSS, JavaScriptなので、それらを省略できる)など、かなり簡略化しています。これらは互換性の問題がなく、記述量を減らすことができるので積極的に使うことができます。

script要素

script要素といえば、内部に直接JavaScriptを書くときにはコメント(<!-- -->)で囲むものだと思われている方が多いかもしれません。入門書などでは「script要素に対応していないブラウザのためにコードがそのまま表示されないようにコメントで囲む」と説明されていることが多いようです。しかし、実際にscript要素に対応していないブラウザはNetscape Navigator 1くらいのものです。古い携帯電話に搭載されているブラウザもscript要素に対応していないことがあるようですが、その場合script要素に関係なくまともに表示できないので、必要なら携帯専用ページを用意するべきでしょう。

なお、外部のJavaScriptファイルを読み込む場合はなるべくcharsetで文字コードを指定しておくとよいでしょう。JavaScriptファイルに日本語のコメントを入れてなかったり、HTMLの文字コードと揃えてある場合は省略しても問題ありませんが、いずれにしても書いておいて損はありません。また、レスポンスヘッダのContentTypeで文字コードを記述するという方法もあります。

また、必要に応じてnoscriptでJavaScriptを無効にしている場合にも配慮しておくとよいでしょう。最近ではJavaScriptに対応していないのではなく、ユーザーがJavaScriptをあえて無効にしているケースが増えています。JavaScriptを有効にすることでどういった機能が使えるのか説明しておけば、ユーザーは必要に応じてJavaScriptを有効にするでしょう。

HTMLの解釈とJavaScriptの実行

ウェブブラウザはHTMLの解析中にscript要素を見つけたらその場でそのJavaScriptを(外部スクリプトの場合は読み込み後に)実行します。その間はそれ以降のHTMLの解析が止まり、ユーザーの操作も受け付けない状態になります。そのため、script要素を記述する位置、その内容には十分に配慮する必要があります。

特に、HTMLの解析を止めて実行されるので、そのscript要素以降にあるHTMLはまだ読み込まれていないためJavaScriptからアクセスできないという問題があります。それを回避する一般的な方法がonloadイベントです。しかし、onloadは画像の読み込みを含む完了なので、完了まで時間がかかってしまうことも珍しくありません。そこでよく使われる方法がscript要素をHTMLの最後(bodyタグを閉じる直前)に記述する方法と、DOMContentLoadedというHTMLの解釈が終わったタイミングで実行させる方法です。

script要素を最後に記述する方法は簡単でわかりやすく、どのブラウザでも動作するので、HTMLを編集できるケースならこの方法がおすすめです。DOMContentLoadedはIEへの対応が少々手間ですがHTML側を編集しなくてもよいというメリットがあります。Firefoxや、Safari 3.1以降、Opera 9、ChromeなどはDOMContentLoadedをサポートしているので次のように記述できます。

DOMContentLoadedの利用例
document.addEventListener("DOMContentLoaded", function(){
  // HTMLの読み込み後に実行されるので、要素を参照できる
  var element = document.getElementById(ID);
}, false);

IEでも動作する読み込み待ち関数は次のとおりです。なお、⁠サポートするか微妙なラインではありますが)Safari 3.0.4などもDOMContentLoadedをサポートしていないので注意が必要です。

DOMContentLoadedの実装例
function dom_ready(callback){
  var isLoaded = false;
  if (document.addEventListener){
    document.addEventListener("DOMContentLoaded",function(){
      callback();
      isLoaded = true;
    }, false);
    // addEventListenerをサポートしているが、DOMContentLoadedを
    // サポートしていないブラウザ用にloadイベントもセットしておく
    window.addEventListener("load", function(){
      if (!isLoaded) callback();
    }, false);
  } else if (window.attachEvent) { // for IE
    if (window.ActiveXObject && window === window.top) {
      _ie();
    } else {
      window.attachEvent("onload", callback);
    }
  } else {
    var _onload = window.onload;
    window.onload = function(){
      if (typeof _onload === 'function') {
        _onload();
      }
      callback();
    }
  }
  function _ie(){
    try {
      document.documentElement.doScroll("left");
    } catch( error ) {
      setTimeout(_ie, 0);
      return;
    }
    callback();
  }
}

IEの場合、読み込み中にdoScrollというメソッドを呼び出すとエラーになる特徴を利用して、エラーが発生しなくなったら読み込みが完了したと判断するようになっています。なお、IE 9はaddEventListener、DOMContentLoadedをサポートする予定です。

dom_ready関数の利用例
dom_ready(function(){
  // HTMLの読み込み後に実行されるので、要素を参照できる
  var element = document.getElementById(ID);
});

HTMLの解釈

HTMLは、タグによっては終了タグを省略することや、開始タグすら省略することが可能な場合があります。終了タグを省略可能なタグとしては、pタグやliタグなどがあります。pタグは内部にインライン要素(spanタグなど)しか持つことができないため、ブロック要素が現れたところでpタグを終了します。つまり、pタグの中にdivタグを入れることはできません。うっかり間違ったHTMLを書いてしまうとJavaScriptからHTMLを操作する際にブラウザによって構造が異なることになって苦労してしまうかもしれません。

タグの省略でよく問題となるのがtbodyタグです。tableタグはcaption、colgroup、thead、tfoot、tbody、col(ただしcolはHTML5でcolgroupの子要素に限定されています)しか子要素に持つことができません。tableタグの直下にtrタグを書いた場合、ブラウザはtbodyタグがあるものとして解釈します。よって、tr要素の親がtableタグだと思ってJavaScriptを書いてしまうとバグを発生させかねません。ほかにもbodyタグを省略したとき、headに含めることができないタグが現れたところにbodyタグが省略されているものとして解釈します。

とはいえ、HTMLを正しく書けていればブラウザ間の差に悩まされることはまずありません。一方、間違ったHTMLを書いてしまうとブラウザの解釈の違いに悩まされることになります。具体例としては、script要素は閉じタグを省略することはできませんが(<script src="~" />のような書き方も不可⁠⁠、うっかり省略してしまっても問題ないブラウザもあるため、すぐに気が付かないこともあるので注意が必要です。

Document Object Model

Document Object Model(DOM)とは、HTMLやXMLなどのマークアップ言語をアプリケーションから扱うためのAPIです。HTML上の要素やテキストを取得したり、新しく作ったり、書き換えたりといった操作が定義されています。HTML上で実行されるJavaScriptはこのDOMを介してHTMLを扱います。そのため、DOMはJavaScriptでアプリケーションを作る際には避けては通れない重要なAPIです。

DOMについて背景知識を簡単に紹介しておきましょう。DOMはW3Cによって定義・勧告されており、2010年5月時点でDOM Level 1からLevel 3まで存在し、さらにその中にいくつもの仕様があるというように、かなり大きな仕様群です。そのうち、IEはDOM Level 1まで、Firefox、Chrome、Safari、OperaなどはDOM Level 2とLevel 3の一部を実装しています。IE 9はDOM Level 2、DOM Level 3をサポートする予定です。

また、DOM Level 1が定義される前にブラウザが実装していて、標準化はされていないがある程度互換性がある部分をDOM Level 0と(慣例で)呼びます。DOM Level 0はHTML5やCSSOMなどで再定義されようとしている部分も多く、標準化が進められています。

IEはDOM Level 2をサポートしていませんが、独自の実装でDOM Level 2に近いAPIが提供されています。この独自の実装が非常にやっかいで、IE以外のブラウザもDOM周りには非互換な部分があるため、クロスブラウザなJavaScriptとはすなわちDOMを扱うことだと言っても過言ではありません。逆に言えば、DOMの扱い方を覚えれば、クロスブラウザは全く難しくなくなります。

DOM Level 1

前述の通り、DOM Level 1まではIEもサポートしているので、同じソースコードでJavaScriptを動かすことができます。例えば、この連載の中でも何度か登場している document.getElementById はDOM Level 1なので下準備や分岐などせずにそのまま使用できます。一方 addEventListener はDOM Level 2 Eventsなので、IE対策でaddEvent関数を定義しました。よって、DOM Level 1を把握することで共通のソースコードでよい部分、IE用に処理を分ける必要がある部分が見えてくるので、DOM Level 1を知っておくことは重要です。

DOM Level 1の範囲で書いたJavaScript
var element = document.getElementById('ID');
var divs = element.getElementsByTagName('div');
for (var i  = 0, len = divs.length;i < len;i++){
  var div = divs[i];
  if (div.className === 'CLASS'){
    div.appendChild(document.createTextNode('TEXT'));
  }
}

document.getElementById, ELEMENT.getElementsByTagName, ELEMENT.className, ELEMENT.appendChild, document.createTextNodeなどはどれもDOM Level 1で定義されているため、このままでクロスブラウザなコードとして動作します。これら一つ一つを把握するのは楽ではありませんが、DOM Level 1 仕様書から、IDL Definitionの部分だけでもざっくり眺めておくとよいと思います(10年前の仕様書なので、それ以上読み込むのはおすすめしません。もし読み込むならその後にDOM Level 2、3も読み込むべきでしょう⁠⁠。

まとめ

今回はJavaScriptのためのHTMLを中心に解説しました。次回からはDOMについて実用的なコードを解説していきたいと思います。

おすすめ記事

記事・ニュース一覧