連載
» 2011年02月07日 10時00分 公開

初心者のためのJavaScript入門(7):JavaScriptでDOMに挑戦 (2/2)

[小島尚基,有限会社インテレクトキューブ]
前のページへ 1|2       

DOMの弱点とその対策

 慣れてしまえば便利なDOMですが、「ブラウザによってドキュメントツリーの構造が違う」という弱点もあります。

 例えば先ほどのサンプルはInternet Explorer 8用のDOMなので、Firefox(3.6.12)やGoogle Chrome(7.0.517.44)ではエラーが起きて、正しく動きません。

 IE以外では、サンプルの10行目を次のように書き換える必要があります。

alert(document.firstChild.childNodes[1].childNodes[1].firstChild.nodeValue);

 どうしてこのようなことが起きてしまうのでしょうか?

 このサンプルでもそうですが、ほとんどの人はJavaScriptやHTMLのソースを書くときには、読みやすくなるようにスペースやタブを使って段落を付けたり、改行を使うと思います。

 ところが、IE以外のDOMでは、HTML内で使っている「スペース、タブ、改行」が、いちいちすべてテキストノード扱いになってしまうのです。つまり、改行のたびに、空白のテキストノードができてしまうことになります。

 このようなテキストノードのことを「ホワイトスペースノード」と呼びます。図3が、Firefox(3.6.12)のドキュメントツリーになります。ホワイトスペースノードにはスペースと改行が入っています。ドキュメントツリーだけでもめんどうなのに、ブラウザでも違いが出るなんて……。

図3:ホワイトスペースノードが含まれるDOMツリーの例 図3:ホワイトスペースノードが含まれるDOMツリーの例

 「それもこれも、普通のオブジェクトのように名前で参照できれば簡単なのに!!」と思いませんか? DOMの基本を見てほしかったので少し遠回りをしましたが、実はDOMには親子関係以外にも、ちゃんと便利な機能が用意されています。

HTMLのタグ名で参照する

 「だったら最初からそっちを教えてくれればいいのに!」という声が聞こえてきそうです。でも、タグ名で参照できるといっても、DOMのドキュメントツリーを理解していないと正しく使うことはできません。

 DOMを扱うということは、ドキュメントツリーの親子関係や兄弟関係と、ノードを正しく理解できてこそなのです。そうは言っても、やってみるのが一番理解できますね。

 簡単なサンプルを見てみまししょう。

<html>
  <body>
    <div>1個目のdivタグです</div>
    <div>2個目のdivタグです</div>
    <div>3個目のdivタグです</div>
    <script>
      alert(document.getElementsByTagName("div")[1].firstChild.nodeValue);
    </script>
  </body>
</html>

 何の変哲もない、3個のdivタグを持ったHTMLですが、この中の2個目のdivタグの中身だけをアラートで表示するDOMが赤で示した7行目に書いてあります。

getElementsByTagName( getElementsByTagName("div")[1]はdivタグの2つ目を指す

 「document.getElementsByTagName("div")」と書くと、HTML内のdivタグのノードが配列で返ってきます。配列で管理されているので、配列の何番目と指定するだけで簡単に使うことができます。divタグの指定方法は、

1番目 document.getElementsByTagName("div")[0]
2番目 document.getElementsByTagName("div")[1]
3番目 document.getElementsByTagName("div")[2]
divタグの指定方法

となります。

 この方法なら、指定されたタグまでのDOMは自動的に処理されるので、ブラウザの違いを気にしなくても良くなります。

DOMでHTMLを書き換える

 DOMを使って、HTML上のノードの中身が参照できるようになりました。次は、参照するだけではなく、その中身を書き換えてみましょう。

<html>
  <body>
    <div>1個目のdivタグです</div>
    <div>2個目のdivタグです</div>
    <div>3個目のdivタグです</div>
    <script>
      document.getElementsByTagName("div")[1].firstChild.nodeValue="ここが書き換わりました";
    </script>
  </body>
</html>

 先ほどのサンプルの7行目で、参照したDOMに「ここが書き換わりました」という文字を代入しています。アラートで確認したように、「document.getElementsByTagName("div")[1].firstChild.nodeValue」は、元々「2個目のdivタグです」が入っていました。では、DOMに別の文字や値を代入するとどうなるでしょうか?

2行目が書き換わる 2行目が書き換わる

 このように、HTMLの内容をDOMを使って簡単に書き換えることができてしまいます。また、複数のノードが配列化されているので、ループと組み合わせることで、一気に処理することもできます。

<html>
  <body>
    <div>1個目のdivタグです</div>
    <div>2個目のdivタグです</div>
    <div>3個目のdivタグです</div>
    <script>
      var dt=document.getElementsByTagName("div");
   for(var i=0; i<dt.length; i++){
        dt[i].firstChild.nodeValue="ここが書き換わりました";
      }
    </script>
  </body>
</html>

すべての行が書き換わる すべての行が書き換わる

idを使って参照する

 ドキュメントツリーの親子関係を使っての参照、タグ名での参照はマスターできたでしょうか?

 しかし、サンプルのような簡単なツリー構造のHTMLならいいですが、実際のWebサイトで、いちいち親子関係や、タグの場所を調べていたら大変なことになります。そこで登場するのが「id」を使ってDOMを参照する方法です。

<html>
  <body>
    <div id="main">
      <div id="main_text">メインのdivタグです</div>
    </div>
    <div id="sub">
      <div id="sub_text">サブのdivタグです</div>
    </div>
    <script>
      alert(document.getElementById("main_text").firstChild.nodeValue);
    </script>
  </body>
</html>

 HTMLでは、「ひとつのドキュメント中に、複数の要素が同じidを持つことは許されない」と決まっているので、文法的に間違った書き方をしなければ、このような階層になっているHTMLでも直接参照することができます。

idを使って直接特定のdivタグを参照した例 idを使って直接特定のdivタグを参照した例

 この「getElementById("id名")」を使うことで、親子関係をあまり気にせずに、名前による参照に近い感覚でDOMが扱えるのです。またもや「こんな便利な方法があるなら、先にそれを教えてくれれば」という声が聞こえてきそうですが、

ケースバイケースで色々な方法を使い分ける必要があります。

 例えば、HTML内の大量の要素を処理する場合は、「getElementById」で1個ずつのidを処理するより、「getElementsByTagName」で配列化された要素を、ループで一気に処理するほうが楽なこともあります。

 ここで紹介した他にも、「getElementsByName」というname属性値を使ってDOMを参照する方法もあります。これは使いどころが少し難しいので今回は説明を省きます。ステップアップのために、是非自分で調べてチャレンジしてみてください。

最後にブックマークレットの解説

 今回は最終回なので課題はありませんが、代わりに第1回でやったブックマークレットの解説をします。

 第1回ではじめてJavaScriptに触れた人は、最初は何が起こっているか理解できなかったと思いますが、この連載を一通り読んできていれば、すでにJavaScriptの地力が付いているはずです。

 連載の最初に見たブックマークレットはこれです。

 @ITトップページなどを表示して、アドレスバーに下のブックマークレットを1行でコピーして、Internet Explorerのアドレスバーに貼り付けてみてください。

※Internet Explorer 6以上で実行してください。

javascript:var r=0;var es = document.images;var ln = es.length;setInterval(function(){for (var i = 0; i < ln; i++) {var cs= es[i].style; cs.position = 'absolute';var red = i*10+Math.PI * r/180; cs.top= 200*Math.cos(red)+200; cs.left= 200*Math.sin(red)+200;}r+=5},5); void(0)

ブックマークレットはアドレスバーに入力するために、改行をしていませんが、分かりやすくするために改行を入れて、詳しく見てみましょう。

var r=0;
var es = document.images;
var ln = es.length;
setInterval(
  function(){
    for (var i = 0; i < ln; i++) {
      var cs= es[i].style;
      cs.position = 'absolute';
      var red = i*10+Math.PI * r/180;
      cs.top= 200*Math.cos(red)+200;
      cs.left= 200*Math.sin(red)+200;
    }
    r+=5
  },5
);
void(0)

1行目:var r=0;
 画像を回転させるための角度を入れる変数rを用意しています。最初は0にしておきます。

2行目:var es = document.images;
 「document.images」というドキュメントオブジェクトを使うと、HTML上の<img>タグで表示されている画像が配列化できます。この画像配列を変数esに入れています。

3行目:var ln = es.length;
 画像配列の画像をループで一気に処理するために、画像配列内の画像の数を変数lnに入れています。配列の長さ(数)は「配列名.length」で調べることができます。

4行目:setInterval(
 「setInterval」はタイマー関数、決まった時間ごとに()の中の関数を繰り返し実行することができます。他にも、JavaScriptのタイマー関数には「setTimeout」があります。「setTimeout」は「setInterval」と違って、決まった時間が来たら一度だけ()の中の関数を実行してくれます。この連載ではタイマー関数の説明はしませんでしたが、是非自分でも使い方を調べてみてください。

5行目:function(){
 4行目の「setInterval」によって、決まった時間ごとにこの関数が実行されます。普通は「setInterval('kansuu()',1000);」のように、実行したい関数の名前を書きますが、ブックマークレットなので関数を分けて書かずに、直接実行させてしまいます。
 このような、名前を持たない関数を「無名関数」と呼びます。名前がないので、普通の名前のある関数とは違って、他からは呼び出して使うことができませんが、名前を付ける必要がないので「ここだけでしか使わない」というのが分かっていればいれば、無名関数で書くほうが簡単なこともあります。

6行目:for (var i = 0; i < ln; i++) {
 変数lnには、HTMLで使われている画像の数が入っているので、画像の数だけループが繰り返されます。

7行目:var cs= es[i].style;
 変数esには画像が配列で入っているので、例えば「es[0]」なら、最初の<img>タグの画像ということになります。「es[0].style」とすることで、この画像のスタイルシートを参照して、変数csに入れています。

8行目:cs.position = 'absolute';
 画像を自由に動かすために、スタイルシートで画像の表示位置を「absolute」にしています。

9行目:var red = i*10+Math.PI * r/180;
 画像を配置するために、画像毎に角度を変えています。「Math.★」というのは、数学関係を扱うオブジェクトです。「Math.PI」は円周率πのことです。画像の回転のために円周率や三角関数を使っていますが、苦手な人は「こういうもの」だと思ってください。計算はブラウザが勝手にやってくれますので、難しく考えないことが大切です。

10行目:cs.top= 200*Math.cos(red)+200;
 画像を配置する縦位置を決めています。「Math.cos」は三角関数のコサインのことです。半径200ピクセルになるように画像を配置しています。画面からはみ出ないように、「+200」して半径分下にずらしています。

11行目:cs.left= 200*Math.sin(red)+200;
 画像を配置する横位置を決めています。「Math.sin」は三角関数のサインのことです。半径200ピクセルになるように画像を配置しています。画面からはみ出ないように、「+200」して半径分右にずらしています。

12行目:}
 for文の終わりです。

13行目:r+=5
 すべての画像を処理し終わったら、画像を回転させるために1行目で設定した変数rに角度を足しています。つまり、無名関数が実行されるたびに、変数rは5ずつ増えていきます。

14行目:},5
 無名関数(function())の終わりのカッコと、後ろの「,5」は「setInterval」が関数を実行する間隔です。「setInterval」の実行間隔はミリ秒(1秒の1/1000)単位で設定できます。この数字を増やすほど、関数が呼び出される間隔が長くなります。ゆっくり動かしたいときはここの数字を大きく、早く動かしたいときは小さくしてみてください。

15行目:);
 「setInterval」の終わりのカッコです。

16行目:void(0)
 これがないと、アドレスバーに貼りつけられたブックマークレットがリンクとして実行されてしまいます。画面が遷移しないためのおまじないのようなものと思ってください。

おわりに

 この連載は、@IT(アットマーク・アイティ)には珍しい「初心者向け」の連載でした。この連載を足がかりに、より難しい他の連載を読んでもらえると、もっともっとJavaScriptが楽しくなってくると思います。

 連載の第1回目を読んだときには「JavaScriptってなんだか分からない」と思っていた人も、なんとなくJavaScriptで何が行われているかが理解できるようになってもらえていたら幸いです。

前のページへ 1|2       

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。