ウェブページやアプリを書く場合に、最も多く必要になるのはウェブ文書をどうかして操作する事でしょう。これは普通ドキュメントオブジェクトモデル (Document Object Model、DOM) によって為され、DOM は HTML とスタイルに関する情報を Document
オブジェクトを多用して操作する一連の API です。この記事では、DOM の使い方を詳しく見ながら、面白い方法であなたの環境を変える事ができる興味深い他の API もいくつか見ていきます。
前提条件: | 基本的なコンピュータに関する知識と理解、HTML と CSS、JavaScript—JavaScript のオブジェクトについても—基本を理解していること |
---|---|
目的: | DOM API の核と、DOM と共によく利用される API、ドキュメントの操作について詳しくなること |
ウェブブラウザーの重要なパーツ
ウェブブラウザーはとてもたくさんの動いている部品からなるソフトウェアの複雑な集合体で、部品の多くはウェブ開発者の JavaScript からでは制御したり操作することはできません。こんな制約はよろしくないと思う方もいるかもしれませんが、ブラウザが保護されているのには十分な理由があって、これは主にセキュリティ関係のためです。もしあるウェブサイトがあなたが保存しているパスワードやその他の秘密情報にアクセスできて、あなたのふりをして他のサイトにログインできたらどうですか?
制限はあっても、ウェブ API は、ウェブページ上でいろいろ素敵な事をできるように、たくさんの機能を提供してくれます。あなたのコードからよく参照するであろう目に見える代物はほんのわずかです — 下の図を見て下さい、この図はウェブページの表示に直接関与しているブラウザーの主要なパーツを表わしています:
- ウィンドウはウェブページが読み込まれる部分の回りのブラウザーの枠です。これは JavaScript では
Window
オブジェクトで表わされます。このオブジェクトに備わるメソッドを使って、ウィンドウの大きさを調べたり (Window.innerWidth
とWindow.innerHeight
を参照)、ウィンドウに読み込まれる文書を操作したり、その文書に関係するデータをクライアント側(例えばローカルデータベースや他のデータ保存機構)で保存したり、現在のウィンドウに対してイベントハンドラー を追加したり、などできます。 - ナビゲータはブラウザーの状態やウェブで使われているようなブラウザーの身元(つまりユーザーエージェント)を表わします。JavaScript では
Navigator
オブジェクトで表わされます。このオブジェクトを使って、位置情報、ユーザが好む言語、ユーザのウェブカムからの録画データ、などを取得できます。 - ドキュメント(ブラウザーでは DOM として表現されます)はウィンドウに実際に読み込まれているページのことで、JavaScript では
Document
オブジェクトで表わされます。このオブジェクトを使って文書を構成する HTML と CSS 上の情報を調べたり操作したりできて、例えば DOM の中のある要素に対する参照を得たり、その中身のテキストを変更したり、新しいスタイルを適用したり、新しい要素を作成して現在の要素の子に追加したり、一緒くたに削除したりできます。
この記事では主にドキュメントの操作に着目しますが、それ以外の役に立つこともちょっとお見せしていきます。
ドキュメントオブジェクトモデル
あなたのブラウザーの一つ一つのタブに今読み込まれているドキュメントは、ドキュメントオブジェクトモデルとして表現されます。これは HTML の構造に対してプログラム言語から簡単にアクセスできるようにブラウザーが作成する、"木構造"による表現です — 例えば、ページをレンダリングする際にはブラウザー自体がスタイルや他の情報を適切な要素に適用するために DOM を使い、ページのレンダリングが終わった後にはあなたのような開発者が JavaScript を使って DOM を操作できます。
dom-example.html にちょっとした例を作成しました(ライブ実行もどうぞ)。ブラウザーから開いてみてください — これはとても簡素なページで、<section>
要素の中に画像が一つと、一つのリンクを含む一つのパラグラフがあります。HTML のソースはこんな感じです:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Simple DOM example</title>
</head>
<body>
<section>
<img src="dinosaur.png" alt="A red Tyrannosaurus Rex: A two legged dinosaur standing upright like a human, with small arms, and a large head with lots of sharp teeth.">
<p>Here we will add a link to the <a href="https://www.mozilla.org/">Mozilla homepage</a></p>
</section>
</body>
</html>
一方これの DOM はこんな具合になります:
注記: この DOM ツリーの図は Ian Hickson の Live DOM viewer を使って作成しました。
これを見ると、それぞれのドキュメント内の要素とちょっとばかりのテキストそれぞれが、ツリーの中でそれ自身のエントリーがあるのがわかるでしょう — これら一つ一つをノードと呼びます。またノードの種類を示す語や、ノードそれぞれの関係によりツリーでの位置があるのがわかるでしょう:
- エレメント(要素)ノード: DOM の中での HTML 要素です。
- ルート(根)ノード: 木の頂点のノードで、HTML の場合であれば常に
HTML
ノードになります。(SVG や独自の XML といった他のマークアップ言語の方言では異なるルート要素の場合があります) - 子ノード: 他のノードに直結して含まれるノードです。上の例だと、例えば
IMG
はSECTION
の子ノードとなります。 - 子孫ノード: 他のノードにどのような形であれ含まれるノードです。上の例だと、例えば
IMG
はSECTION
の子ノードであり、子孫ノードでもあります。IMG
はBODY
の二段階内側にあるのでBODY
の子ノードではありませんが、BODY
の子孫ノードではあります。 - 親ノード: その中に他のノードを持つノードです。例えば上の例だと
BODY
はSECTION
ノードの親ノードになります。 - 兄弟ノード: DOM ツリーの同じ階層にあるノードです。上の例だと
IMG
とP
は兄弟ノードになります。 - テキストノード: テキスト文字列を含むノードです。
これからコードを見ていくとこういう語が頻出するので、DOM を使い始める前に、これらの用語をしっかり覚えておくと良いでしょう。CSS の勉強をしているときも、これらの語をみかけることでしょう(子孫セレクター、子セレクターとか)。
実践学習: 基本的なDOM操作
DOM 操作の学習スタートは、実践的な例から始めましょう。
- dom-example.html と image のローカルコピーを一緒に作成して下さい。
<script></script>
要素を、閉じ</body>
タグのすぐ上に追加して下さい。- DOM の中の要素を操作するため、まず DOM を選びだしてこれへの参照を変数に保存する必要があります。script 要素の中に、次の行を追加して下さい:
const link = document.querySelector('a');
- 要素への参照を変数に保存したので、これが備えているプロパティとメソッドを使って DOM の操作を始められます (利用できるプロパティとメソッドは、たとえば
<a>
要素であればHTMLAnchorElement
インターフェース、さらにその汎化した親のインターフェースHTMLElement
やNode
— これは DOM の全てノードが相当します — で定義されています)。まずは、リンクの中のテキストを、Node.textContent
プロパティを更新する事で変更してみましょう。上で書いた行の下に、次の行を追加して下さい:link.textContent = 'Mozilla Developer Network';
- クリックされたときに変な場所に行かないよう、リンクが指す先の URL も変えておくべきでしょう。また下に、以下の行を追加して下さい:
link.href = 'https://developer.mozilla.org';
JavaScript あるあるですが、要素を選んで変数に保存する方法にはいろんなやり方があることを頭に入れておいて下さい。Document.querySelector()
を使うのが推奨される今風のやり方ですが、これは CSS セレクタと同じ方法で要素を選別できるからです。上記の querySelector()
呼び出しでは文書に現われる最初の <a>
がマッチします。もし複数の要素を選択し処理したいのであれば Document.querySelectorAll()
を使うことができて、これはセレクタとマッチする全ての要素にマッチし、それらへの参照を NodeList
と呼ばれる配列のようなオブジェクトに保存します。
要素への参照を得るための、次のような古いやり方もあります:
Document.getElementById()
は要素を指定のid
属性値を使って選択します。<p id="myId">My paragraph</p>
こんなのです。 関数の引数に ID を渡します。const elementRef = document.getElementById('myId')
こんな具合です。Document.getElementsByTagName()
これは指定した種類の全ての要素を配列のようなオブジェクトとして返します、例えば全部の<p>
、全部の<a>
など。 要素の種別は関数の引数として渡します。const elementRefArray = document.getElementsByTagName('p')
こんな具合です。
上の二つは querySelector()
のような今風のメソッドよりも古いブラウザーで動作しますが、あまり便利ではありません。これ以外にどんなやり方があるかは、あなた自身で探してみて下さい!
新しいノードの作成と配置
ここまでで、どんな事ができるのかちょっと見えてきたと思いますが、さらに進んで新しい要素を作る方法を見ていきましょう。
- 今の例題に戻って、
<section>
要素を掴むところから始めましょう — すでに書いてあるスクリプトの下に次のコードを追加して下さい(この先の他の行についても、同じようにやって下さい):const sect = document.querySelector('section');
Document.createElement()
を使って新しいパラグラフを作り、前やったのと同じ方法でテキストを入れてやりましょう:const para = document.createElement('p'); para.textContent = 'We hope you enjoyed the ride.';
- この新しいパラグラフを section の最後に
Node.appendChild()
を使って追加できます:sect.appendChild(para);
- このパートの締めとして、文章がうまいことまとまるように、リンクを含んでいるパラグラフに対してテキストノードを追加しましょう。まずテキストノードを
Document.createTextNode()
を使って作成します:const text = document.createTextNode(' — the premier source for web development knowledge.');
- リンクを含んだパラグラフへの参照を取り出して、そこにテキストノードを追加します:
const linkPara = document.querySelector('p'); linkPara.appendChild(text);
以上が DOM にノードを追加するために必要な事のほぼ全てです — 動的なインターフェースを作成する際(あとでそういう例題をいくつか見ていきます)これらのメソッドをめっちゃ使う事になるでしょう。
要素を移動したり削除したり
ノードを移動したり、DOM から削除したくなる場合があると思います。勿論できます。
リンクを含むパラグラフを section の最後に移動したい場合は、こうするだけです:
sect.appendChild(linkPara);
これでパラグラフは section の一番下に移動します。コピーが作成されるだけじゃないのかとお思いかもしれませんが、この場合は違います — linkPara
はパラグラフへの参照の唯一のコピーです。もしコピーをした上で同じように追加をしたいのであれば、 Node.cloneNode()
をかわりに使う必要があります。
削除したいノードとその親ノードへの参照を得ていれば、ノードを削除するのも非常に簡単です。今の例題であれば、以下のように Node.removeChild()
を使うだけです:
sect.removeChild(linkPara);
よくあるケースですが、削除したいノードそのものへの参照しかない場合に、ChildNode.remove()
が使えます:
linkPara.remove();
このメソッドは、古いブラウザではサポートされていません。 ノードにそれ自体を削除するように指示するメソッドはないので、次のようにしなければなりません。
linkPara.parentNode.removeChild(linkPara);
上の行をあなたのコードに追加してやってみて下さい。
スタイルを操作する
いろんなやり方で CSS スタイルを JavaScript から操作することができます。
まず、ドキュメントに付随する全部のスタイルシートのリストは Document.stylesheets
を使って得られ、これは CSSStyleSheet
オブジェクトを含む配列のようなオブジェクトを返します。そうしたらお望みのままにスタイルを追加したり削除したりできます。ですがこのやり方について詳しくはやりません。なぜならスタイルをいじるにはちょっとばかり古風で難しいやり方だからです。もっと簡単なやり方があります。
まずは、動的にスタイルを指定したい要素に、インラインスタイルを直接追加するやり方です。これには HTMLElement.style
プロパティを使い、このプロパティはドキュメント中の各素要のインラインスタイル情報を保持しています。このオブジェクトのプロパティを更新すれば要素のスタイルを直接変更できます。
- 例として、作成中の例題に以下の行を追加してみて下さい:
para.style.color = 'white'; para.style.backgroundColor = 'black'; para.style.padding = '10px'; para.style.width = '250px'; para.style.textAlign = 'center';
- ページをリロードすると指定のパラグラフにスタイルが適用されているはずです。ブラウザーの Page Inspector や DOM inspector からパラグラフを見ると、言うまでもなく上の行がドキュメントのインラインスタイルに追加されているはずです:
<p style="color: white; background-color: black; padding: 10px; width: 250px; text-align: center;">We hope you enjoyed the ride.</p>
注記: CSS ではハイフン記法になっているものを、JavaScript プロパティ版の CSS スタイルはどんな風に小文字のキャメルケースで書いている(background-color
と backgroundColor
とか)か見ておいて下さい。まぜこぜにしないよう注意して下さい、さもないと動きませんよ。
ドキュメントのスタイルを動的にいじる際によく使われる別のやり方をこれから見ていきましょう。
- さっき JavaScript に追加した 5 行を削除します。
- HTML の
<head>
の中に、以下を追加します:<style> .highlight { color: white; background-color: black; padding: 10px; width: 250px; text-align: center; } </style>
- さて、多くの HTML 操作においてとても役に立つメソッドをお見せします —
Element.setAttribute()
— これはに二つの引数、要素に設定したい属性名と、属性に設定したい値、を与えます。この場合だと、我々のパラグラフにクラス名、highlight をセットします:para.setAttribute('class', 'highlight');
- ページをリロードしても何も変わりません — パラグラフには CSS が今も適用されていますが、今回はクラスを指定して CSS ルールが選んでいて、インライン CSS スタイルによるものではありません。
どうやるかはあなた次第です。それぞれに利点と欠点があります。最初のやり方は少ない設定ですみ、簡単な場合には向いていますが、二つ目のやり方はずっときれいです (よくないやり方とされる、CSS と JavaScript の混在やインラインスタイルの使用がありません)。もっと大規模で複雑なアプリを作り始めたら、多分二つ目のやり方をよく使うようになると思いますが、結局はホントにあなた次第です。
ここまで、実はそれほど役に立つことをやってません! 静的なコンテンツの作成に JavaScript を使う利点はありません — JavaScript など使わず、普通に HTML に書けば良いんです。HTML よりややこしいですし、コンテンツを JavaScript で作成するのは他にも問題があります (検索エンジンで読めない、とか)。
次の二つのセクションでは、DOM API のもっと実践的な使い方を見ていきます。
注記: 私たちによる dom-example.htm l完成版 のデモが GitHub にあります (ライブ実行もどうぞー)。
実践学習: ウィンドウオブジェクトから使える情報を取り出す
ここまででは文書を操作するための Node
と Document
の機能ばかり見てきましたが、他のソースからデータを取ってきてあなたの UI で使ったって勿論かまわないわけです。あなたはデータが正しい形式である事を確認するだけです。これは JavaScript が弱い型付け言語であるために、他の多く言語の場合よりも簡単です — 例えば画面に表示しようとしたとき、数値は自動的に文字列に変換されます。
ここの例題ではよくある問題を解決していきます — あなたのアプリを表示しているウィンドウがどんな大きさであれ、それを同じ大きさになるようにすることです。これはゲームのような、表示する画面領域をできるだけ大きくしたいような場合に、しばしば役に立ちます。
まずは window-resize-example.html と bgtile.png ファイルのローカルコピーを作成して下さい。読み込んで見てみて下さい — 背景に画像がタイル表示された、<div>
要素が画面に小さく表示されているでしょう。この領域が、私たちのアプリの UI 領域だとしていきます。
- まず最初に、div への参照を取得し、ビューポート (ドキュメントが表示されている内側のウィンドウです) の幅と高さを取得して、これらを変数に保存します。便利なことに幅と高さの値は
Window.innerWidth
とWindow.innerHeight
プロパティにあります。以下の行を、もう書いてある<script>
の中に書き足します:const div = document.querySelector('div'); let winWidth = window.innerWidth; let winHeight = window.innerHeight;
- 次は、動的に div の幅と高さをビューポートのものと同じにします。次の二行を、さっき追加した部分の後に書き足して下さい:
div.style.width = winWidth + 'px'; div.style.height = winHeight + 'px';
- 保存してブラウザーで読み直してみて下さい — どんな大きさの画面を使っているのであれ、div がビューポートと同じ大きさになったはずです。ウィンドウが大きくなるようにリサイズしてみても、div の大きさは変わらないはずです — 一度しか大きさを設定していないからです。
- ウィンドウがリサイズされた時に div もリサイズされるよう、イベントを使ってみるのはどうでしょう?
Window
オブジェクトにはリサイズされた時に呼ばれるイベントがあって、ウィンドウがリサイズされる毎発火します — この機能をWindow.onresize
イベントハンドラーから使って、リサイズされる毎私たちのコードが再実行されるようにしてみましょう。あなたのコードの最後に以下を書き足して下さい:window.onresize = function() { winWidth = window.innerWidth; winHeight = window.innerHeight; div.style.width = winWidth + 'px'; div.style.height = winHeight + 'px'; }
注記: もし行き詰まったら、私たちによる 完成版ウィンドウリサイズ例題 (ライブ実行版もあるよ) を見て下さい。
実践学習: 動的な買い物リスト
この記事の締めとして、あなたにちょっとした難題を出したいと思います — 単純な買い物リストの例を作ってもらいます。フォーム入力(input)とボタンからリストに動的に商品を追加できるようにします。input に商品を入力してボタンを押したら:
- 商品がリストに表示されなければならない。
- それぞれの商品にはボタンが付いていて、それを押すとその商品をリストから消せなければならない。
- 次の商品をすぐに入力できるよう、input の中身は消されてフォーカスされていなければならない。
完成版のデモはこんな感じになるでしょう:
この課題を完了させるには、以下のステップに従い、上で説明した通りに買い物リストが動くようにして下さい。
- まず私たちが用意した shopping-list.html 初期ファイルをダウンロードしてローカルコピーをどこかに作成します。最小限の CSS、ラベルのついたリスト、inputとボタン、空のリストと
<script>
要素が書いてあるはずです。この先書き足していくものは全部 script の中に書きます。 - (
<ul>
) と<input>
と<button>
要素への参照を保持する3つの変数を作成します。 - ボタンがクリックされた時の応答として走らせる 関数 を作成します。
- 関数本体は、input 要素の現在の 値を変数に保存するところから始めます。
- 次に、input 要素の値に空文字列(
''
)を代入して、input 要素を空にします。 - 3つの要素を作成します — リスト項目(
<li>
) と<span>
と<button>
で、これらを変数に保存します。 - span と button をリスト項目 li の子に追加します。
- spanのテキストコンテントに、先程保存した input 要素の値を代入し、ボタンのテキストコンテントを「削除」にします。
- できたリスト項目をリストの子に追加します。
- 削除ボタンにイベントハンドラーを追加して、クリックされたらボタンが含まれているリスト項目全体を削除するようにします。
- 最後に、
focus()
メソッドを使って input 要素にフォーカスし、次の買い物リスト商品をすぐに入力できるようにします。
注記: 本当にどうしようもなく詰まったら、私たちの 完成版買い物リスト (ライブ実行版もあるよ)を見て下さい。
まとめ
私たちのドキュメントと DOM 操作に関する学習はこれで終わりです。ここまでくれば、ドキュメントの制御やユーザのウェブ体験に関するブラウザーの重要な部品は何か、理解できたと思います。一番大事な DOM とは何か、役に立つ機能を作るのにこれをどう使えば良いのか理解できたと思います。
参考文献
ドキュメントをいじるのに役立つ機能はたくさんあります。私たちのリファレンスも見て、いろいろ発見して下さい:
(私共の Web API index から、MDNにあるウェブAPIに関する全ドキュメント一覧も見て下さい!)