034.4 レッスン 1
Certificate: |
Web開発の要点 |
---|---|
Version: |
1.0 |
Topic: |
034 JavaScriptプログラミング |
Objective: |
034.4 WebサイトのコンテンツとスタイリングのJavaScript操作 |
Lesson: |
1 of 1 |
はじめに
HTML、CSS、そしてJavaScriptは、Web上で一緒になる3つの異なる技術です。真にダイナミックでインタラクティブなページを作るためには、JavaScriptプログラマはHTMLとCSSのコンポーネントを実行時に組み合わせなければなりません。これは、 Document Object Model (DOM)を使用することで非常に簡単になります。
DOMとのインタラクション
DOMは、ドキュメントに対するプログラミング・インターフェースとして機能するデータ構造で、ドキュメントのあらゆる側面がDOMのノードとして表現され、DOMに加えられた変更は直ちにドキュメントに反映されます。JavaScriptでDOMを使う方法を示すために、以下のHTMLコードを example.html
というファイルに保存します。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>HTML Manipulation with JavaScript</title>
</head>
<body>
<div class="content" id="content_first">
<p>The dynamic content goes here</p>
</div><!-- #content_first -->
<div class="content" id="content_second" hidden>
<p>Second section</p>
</div><!-- #content_second -->
</body>
</html>
DOMはHTMLが読み込まれてから利用できるようになりますので、以下のJavaScriptをページボディの最後(末尾の </body>
タグの前)に記述します。
<script>
let body = document.getElementsByTagName("body")[0];
console.log(body.innerHTML);
</script>
document
オブジェクトはDOMの最上位の要素であり、他の全ての要素はそこから分岐しています。 getElementsByTagName()
メソッドは、 document
から続く、与えられたタグ名を持つ全ての要素を列挙します。文書の中で body
タグが一度しか使われていないにもかかわらず、 getElementsByTagName()
メソッドは常に見つかった要素の配列のようなコレクションを返すので、最初に(そして唯一)見つかった要素を返すために [0]
インデックスを使用します。
HTMLコンテンツ
前の例で示したように、 document.getElementsByTagName("body")[0]
が返すDOM要素は、 body
変数に割り当てられました。 body
変数は、そのページのbody要素からすべてのDOMメソッドと属性を継承しているので、body要素を操作するために使用することができます。例えば、 innerHTML
プロパティには、対応する要素の内部に書かれたHTMLマークアップコード全体が含まれているので、内部のマークアップを読み取るために使用することができます。 console.log(body.innerHTML)
の呼び出しは、 <body></body>
内のコンテンツをWebコンソールに表示します。この変数は、 body.innerHTML = "<p>Content erased</p>"
のように、そのコンテンツを置き換えるために使用することもできます。
HTMLマークアップの全体的な部分を変更するよりも、ドキュメントの構造を変更せずに、その要素を操作する方が実用的です。ドキュメントがブラウザによってレンダリングされると、すべての要素がDOMメソッドによってアクセス可能になります。例えば、 document
オブジェクトの getElementsByTagName()
メソッドで、 *
という特別な文字列を使って、すべてのHTML要素をリストアップしてアクセスすることができます。
let elements = document.getElementsByTagName("*");
for ( element of elements )
{
if ( element.id == "content_first" )
{
element.innerHTML = "<p>New content</p>";
}
}
このコードは document
で見つかったすべての要素を elements
変数に格納します。 elements
変数は配列のようなオブジェクトなので、 for
ループを使って各項目を繰り返し処理することができます。このコードが実行されているHTMLページに、 id
属性が content_first
に設定されている要素があれば(レッスンの最初に示したサンプルHTMLページを参照してください)、 if
文がその要素にマッチして、そのマークアップコンテンツが <p>New content</p>
に変更されます。DOM内のHTML要素の属性は、JavaScriptのオブジェクトプロパティの ドット 記法を使ってアクセスできることに注意してください。したがって、 element.id
は、 for
ループの現在の要素の id
属性を指します。また、 element.getAttribute("id")
のように、 getAttribute()
メソッドを使用することもできます。
要素のサブセットのみを検査したい場合は、すべての要素を繰り返し調べる必要はありません。例えば、 document.getElementsByClassName()
メソッドは、マッチした要素を特定のクラスを持つものに限定します。
let elements = document.getElementsByClassName("content");
for ( element of elements )
{
if ( element.id == "content_first" )
{
element.innerHTML = "<p>New content</p>";
}
}
しかし、ページ内の特定の要素を変更しなければならない場合、ループを使用して多くのドキュメント要素を繰り返し処理することは最良の戦略ではありません。
特定の要素の選択
JavaScriptには、作業したい要素を正確に選択するための最適化されたメソッドが用意されています。先ほどのループは、 document.getElementById()
メソッドで完全に置き換えることができます。
let element = document.getElementById("content_first");
element.innerHTML = "<p>New content</p>";
ドキュメントのすべての id
属性は一意でなければならないので、 document.getElementById()
メソッドは単一のDOMオブジェクトのみを返します。JavaScriptではメソッドを直接チェーンすることができるので、 element
変数の宣言も省略することができます。
document.getElementById("content_first").innerHTML = "<p>New content</p>";
getElementById()
メソッドは、複雑なドキュメントを扱う際に反復的な手法よりもはるかに優れたパフォーマンスを発揮するので、DOM内の要素を探すのに望ましい手法です。しかし、すべての要素が明示的なIDを持っているわけではなく、このメソッドは、提供されたIDにマッチする要素がない場合には、 null 値を返します(これは、上記の例で使われている innerHTML
のように、連鎖した属性や関数を使うこともできません)。さらに、ページの主要な構成要素にのみID属性を割り当て、その子要素を探すためにCSSセレクタを使用するのがより実用的です。
CSSのレッスンで紹介したセレクタは、DOM内の要素にマッチするパターンです。 querySelector()
メソッドは、DOMのツリーの中で最初にマッチした要素を返し、 querySelectorAll()
は、指定されたセレクタにマッチしたすべての要素を返すようになっています。
前述の例では、 getElementById()
メソッドは、 content_first
のIDを持つ要素を取得しています。これと同じことを、 querySelector()
メソッドでも行うことができます。
document.querySelector("#content_first").innerHTML = "<p>New content</p>";
querySelector()
メソッドはセレクタ構文を使用するため、指定されたIDはハッシュ文字で始まる必要があります。一致する要素が見つからない場合は、 querySelector()
メソッドは null を返します。
前述の例では、idが content_first
のdivのコンテンツ全体が、指定されたテキスト文字列で置き換えられています。この文字列にはHTMLコードが含まれていますが、これはベストプラクティスとは言えません。JavaScriptコードにハードコードされたHTMLマークアップを追加する際には注意が必要です。なぜなら、ドキュメント構造全体の変更が必要になったときに、要素の追跡が困難になる可能性があるからです。
セレクタは要素のIDに限定されません。内部の p
要素を直接指定することができます。
document.querySelector("#content_first p").innerHTML = "New content";
"#content_first p"
セレクタは、idが content_first
の div 内の最初の p
要素のみにマッチします。最初の要素を操作したい場合には問題なく動作します。しかし、2つ目の段落を変更したい場合もあります。
<div class="content" id="content_first">
<p>Don't change this paragraph.</p>
<p>The dynamic content goes here.</p>
</div><!-- #content_first -->
この場合、 :nth-child(2)
擬似クラスを使って、2番目の p
要素にマッチさせることができます。
document.querySelector("#content_first p:nth-child(2)").innerHTML = "New content";
p:nth-child(2)
の数字 2
は、セレクタにマッチする2つ目の段落を示しています。セレクタの詳細や使い方については、CSSセレクタのレッスンをご覧ください。
属性の操作
JavaScriptのDOMとの対話能力は、コンテンツの操作に限定されるものではありません。実際、ブラウザにおけるJavaScriptの最も普及した使い方は、既存のHTML要素の属性を変更することです。
例えば、元のHTMLの例のページに、3つのセクションのコンテンツがあるとします。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>HTML Manipulation with JavaScript</title>
</head>
<body>
<div class="content" id="content_first" hidden>
<p>First section.</p>
</div><!-- #content_first -->
<div class="content" id="content_second" hidden>
<p>Second section.</p>
</div><!-- #content_second -->
<div class="content" id="content_third" hidden>
<p>Third section.</p>
</div><!-- #content_third -->
</body>
</html>
一度に1つだけを表示したい場合があるため、すべての div
タグには hidden
属性が付いています。これは、例えば、画像のギャラリーから1つの画像だけを表示する場合などに便利です。ページが読み込まれたときにそれらのうちの1つを表示するには、次のようなJavaScriptのコードをページに追加します。
// Which content to show
let content_visible;
switch ( Math.floor(Math.random() * 3) )
{
case 0:
content_visible = "#content_first";
break;
case 1:
content_visible = "#content_second";
break;
case 2:
content_visible = "#content_third";
break;
}
document.querySelector(content_visible).removeAttribute("hidden");
switch
文で評価された式は、0、1、2のいずれかの数字をランダムに返します。対応するIDセレクターは、 content_visible
変数に代入され、 querySelector(content_visible)
メソッドで使用されます。連鎖した removeAttribute("hidden")
の呼び出しは、要素から hidden
属性を削除します。
逆のアプローチも可能です。すべてのセクションは最初は( hidden
属性なしで)表示されていて、JavaScriptプログラムは content_visible
にあるセクション以外のすべてのセクションに hidden
属性を割り当てることができます。そのためには、選択されたものとは異なるすべてのコンテンツdiv要素を繰り返し処理する必要がありますが、これは querySelectorAll()
メソッドを使用して行うことができます。
// Which content to show
let content_visible;
switch ( Math.floor(Math.random() * 3) )
{
case 0:
content_visible = "#content_first";
break;
case 1:
content_visible = "#content_second";
break;
case 2:
content_visible = "#content_third";
break;
}
// Hide all content divs, except content_visible
for ( element of document.querySelectorAll(".content:not("+content_visible+")") )
{
// Hidden is a boolean attribute, so any value will enable it
element.setAttribute("hidden", "");
}
content_visible
変数が #content_first
に設定されていた場合、セレクタは .content:not(#content_first)
となり、 content
クラスを持つすべての要素のうち、content_first
のIDを持つものを除いたもの、と解釈されます。 setAttribute()
メソッドは、HTML要素の属性を追加したり変更したりします。このメソッドの最初のパラメータは属性の名前で、2番目のパラメータは属性の値です。
しかし、要素の外観を変更する適切な方法はCSSです。今回の例では、 display
CSSプロパティを hidden
に設定しましたが、JavaScriptで表示を block
に変更しても同じです。
<style>
div.content { display: none }
</style>
<div class="content" id="content_first">
<p>First section.</p>
</div><!-- #content_first -->
<div class="content" id="content_second">
<p>Second section.</p>
</div><!-- #content_second -->
<div class="content" id="content_third">
<p>Third section.</p>
</div><!-- #content_third -->
<script>
// Which content to show
let content_visible;
switch ( Math.floor(Math.random() * 3) )
{
case 0:
content_visible = "#content_first";
break;
case 1:
content_visible = "#content_second";
break;
case 2:
content_visible = "#content_third";
break;
}
document.querySelector(content_visible).style.display = "block";
</script>
HTMLタグとJavaScriptの混在に適用されるグッドプラクティスは、CSSにも適用されます。したがって、JavaScriptのコードの中にCSSのプロパティを直接記述することは推奨されません。その代わり、CSSのルールはJavaScriptのコードとは別に記述する必要があります。ビジュアルスタイリングを交互に行う適切な方法は、要素にあらかじめ定義されたCSSクラスを選択することです。
クラスの操作
要素は複数の関連クラスを持つことができ、必要に応じて追加や削除ができるスタイルを簡単に書くことができます。多くのCSS属性をJavaScriptで直接変更するのは疲れるので、それらの属性を持つ新しいCSSクラスを作成して、そのクラスを要素に追加することができます。DOM要素には classList
というプロパティがあり、これを使って対応する要素に割り当てられたクラスを表示したり操作したりすることができます。
例えば、要素の可視性を変更する代わりに、追加のCSSクラスを作成して、 content
divを強調することができます。
div.content {
border: 1px solid black;
opacity: 0.25;
}
div.content.highlight {
border: 1px solid red;
opacity: 1;
}
このスタイルシートは、 content
クラスを持つすべての要素に、薄い黒のボーダーと半透明を追加します。また、 highlight
クラスを持つ要素のみ、完全に不透明になり、細い赤のボーダーが表示されます。そして、先ほどのようにCSSのプロパティを直接変更するのではなく、選択した要素で classList.add("highlight")
メソッドを使用します。
// Which content to highlight
let content_highlight;
switch ( Math.floor(Math.random() * 3) )
{
case 0:
content_highlight = "#content_first";
break;
case 1:
content_highlight = "#content_second";
break;
case 2:
content_highlight = "#content_third";
break;
}
// Highlight the selected div
document.querySelector(content_highlight).classList.add("highlight");
これまで見てきたテクニックや例は、すべてページ読み込みの最後に行われたものでしたが、この段階に限定されるものではありません。実際、JavaScriptがWeb開発者にとって便利なのは、次に紹介するようなページ上のイベントに反応する能力です。
イベントハンドラー
すべての可視ページ要素は、クリックやマウスの動きなどのインタラクティブなイベントの影響を受けます。これらのイベントにカスタムアクションを関連付けることで、HTMLドキュメントのできることが大きく広がります。
関連付けられたアクションの恩恵を受ける最も明白なHTML要素は、おそらく button
要素でしょう。その仕組みを説明するために、サンプルページの最初の div
要素の上に、3つのボタンを追加してみましょう。
<p>
<button>First</button>
<button>Second</button>
<button>Third</button>
</p>
<div class="content" id="content_first">
<p>First section.</p>
</div><!-- #content_first -->
<div class="content" id="content_second">
<p>Second section.</p>
</div><!-- #content_second -->
<div class="content" id="content_third">
<p>Third section.</p>
</div><!-- #content_third -->
ボタンはそれだけでは何もしませんが、押されたボタンに対応する div
をハイライトしたいとします。そこで、 onClick
属性を使って、それぞれのボタンにアクションを関連付けることができます。
<p>
<button onClick="document.getElementById('content_first').classList.toggle('highlight')">First</button>
<button onClick="document.getElementById('content_second').classList.toggle('highlight')">Second</button>
<button onClick="document.getElementById('content_third').classList.toggle('highlight')">Third</button>
</p>
classList.toggle()
メソッドは、指定したクラスが要素に存在しない場合は追加し、既に存在する場合は削除します。この例を実行すると、複数の div
が同時にハイライトされることに気づくでしょう。押されたボタンに対応する div
だけをハイライトするには、他の div
要素から highlight
クラスを削除する必要があります。とはいえ、カスタムアクションが長すぎたり、1行以上のコードが必要な場合は、要素タグとは別に関数を書いたほうが実用的です。
function highlight(id)
{
// Remove the "highlight" class from all content elements
for ( element of document.querySelectorAll(".content") )
{
element.classList.remove('highlight');
}
// Add the "highlight" class to the corresponding element
document.getElementById(id).classList.add('highlight');
}
前述の例と同様に、この関数は <script>
タグの中や、ドキュメントに関連付けられた外部のJavaScriptファイルの中に置くことができます。 highlight
関数は、まず、 content
クラスに関連付けられたすべての div
要素から highlight
クラスを削除し、次に、選択した要素に highlight
クラスを追加します。そして、それぞれのボタンは、対応するid属性を関数の引数として用いて、その onClick
属性からこの関数を呼び出す必要があります。
<p>
<button onClick="highlight('content_first')">First</button>
<button onClick="highlight('content_second')">Second</button>
<button onClick="highlight('content_third')">Third</button>
</p>
onClick
属性に加えて、 onMouseOver
属性(ポインティングデバイスを使ってカーソルが要素の領域内へ移動したときにトリガーされる)、 onMouseOut
属性(ポインティングデバイスが要素の領域外に出たときにトリガーされる)などを使用することができます。さらに、イベントハンドラはボタンに限定されませんので、表示されているすべてのHTML要素について、これらのイベントハンドラにカスタムアクションを割り当てることができます。
演習
-
document.getElementById()
メソッドを使って、id属性がmessage
である要素のインナーコンテンツに “Dynamic content” というフレーズを挿入するにはどうしたらいいでしょうか? -
document.querySelector()
メソッドで要素のIDを参照する場合と、document.getElementById()
メソッドで要素を参照する場合の違いは何ですか? -
classList.remove()
メソッドの目的は何ですか? -
myelement.classList.toggle("active")
メソッドで、myelement
にactive
クラスが割り当てられていない場合、どのような結果になりますか?
発展演習
-
document.querySelectorAll()
メソッドにどのような引数を与えれば、document.getElementsByTagName("input")
メソッドを模倣することができるでしょうか? -
classList
プロパティを使って、与えられた要素に関連するすべてのクラスをリストアップするにはどうしたらいいでしょうか?
まとめ
このレッスンでは、JavaScriptを使って、DOM(Document Object Model)を利用して、HTMLのコンテンツやそのCSSプロパティを変更する方法を説明しました。これらの変更は、ユーザーイベントによって引き起こされることができ、動的なインターフェースを作成するのに便利です。このレッスンでは、以下の概念と手順を説明しました。
-
document.getElementById()
、document.getElementsByClassName()
、document.getElementsByTagName()
、document.querySelector()
そしてdocument.querySelectorAll()
などのメソッドを使用して、ドキュメントの構造を検査する方法 -
innerHTML
プロパティを使ってドキュメントのコンテンツを変更する方法 -
メソッド
setAttribute()
とremoveAttribute()
を使用して、ページ要素の属性を追加および変更する方法 -
classList
プロパティを使用して、要素のクラスを操作する適切な方法と、CSS スタイルとの関係 -
特定の要素のマウスイベントに関数をバインドする方法
演習の回答
-
document.getElementById()
メソッドを使って、id属性がmessage
である要素のインナーコンテンツに “Dynamic content” というフレーズを挿入するにはどうしたらいいでしょうか?それは
innerHTML
プロパティで実現できます。document.getElementById("message").innerHTML = "Dynamic content"
-
document.querySelector()
メソッドで要素のIDを参照する場合と、document.getElementById()
メソッドで要素を参照する場合の違いは何ですか?document.querySelector()
などのセレクタを使用する関数では、id属性の指定にハッシュ文字(#)を付加する必要があります。 -
classList.remove()
メソッドの目的は何ですか?これは、対応する要素の
class
属性から、(関数の引数として名前が与えられた)クラスを削除します。 -
myelement.classList.toggle("active")
メソッドで、myelement
にactive
クラスが割り当てられていない場合、どのような結果になりますか?このメソッドは
myelement
にactive
クラスを割り当てます。
発展演習の回答
-
document.querySelectorAll()
メソッドにどのような引数を与えれば、document.getElementsByTagName("input")
メソッドを模倣することができるでしょうか?document.querySelectorAll("input")
を使うと、document.getElementsByTagName("input")
と同様に、ページ内のすべてのinput
要素にマッチします。 -
classList
プロパティを使って、与えられた要素に関連するすべてのクラスをリストアップするにはどうしたらいいでしょうか?classList
プロパティは、配列のようなオブジェクトなので、for
ループを使って、含まれるすべてのクラスを繰り返し処理することができます。