JavaScript 2
作成日:2017年4月2日
ブラウザに組み込まれた JavaScript
以下は、主にオライリー・ジャパン JavaScript(第5版)の「Webブラウザに組み込まれたJavaScript」からの一部抜粋です。
クライアントサイド JavaScriptにおいて、HTMLドキュメントに対応するのが Document オブジェクトで、ドキュメントを表示するウィンドウ(フレーム)に対応するのが Window オブジェクトです。
クライアントサイドプログラミングでは Window オブジェクトがグローバルオブジェクトの役目を果たします。クライアントサイド JavaScript では、Window オブジェクトがグローバルオブジェクトなので、どのグローバル変数もウィンドウのプロパティとして定義されることになります。
//次のコードは同じ意味。 var answer = 77; // グローバル変数を宣言し初期化 window.answer = 77; // Windowオブジェクトに新たにプロパティを作成
クライアントサイドオブジェクトの階層構造と DOM
Window オブジェクトは、Web ブラウザによって表示されるウィンドウ、またはウィンドウ内の1つのフレームを表します。
JavaScript アプリケーションを作成する場合、複数のウィンドウやフレームを使ったアプリケーションも作成できます。このとき、 JavaScript コードで宣言されるグローバル変数は、それぞれのウィンドウごとに異なります。
クライアントサイド JavaScript の階層構造で最上位に位置するのが Window オブジェクトです。 クライアントサイドのオブジェクトはすべて、この Window オブジェクトを通してアクセスします。クライアントサイドオブジェクトの階層構造のうち、Document オブジェクトから下位の階層のことを ドキュメントオブジェクトモデル(DOM)と言います。
関連ページ:Window オブジェクトや Document オブジェクト、DOMなど
イベント駆動型のプログラミングモデル
ユーザからの入力があるとWebブラウザがイベントを生成します。イベントが発生すると、Webブラウザは、そのイベントに対応するイベントハンドラを呼び出すので、このイベントハンドラを定義、登録してプログラムを作成します。
HTMLドキュメント内のイベントハンドラ
- onclick
- このハンドラは <a>や<area> タグも含め、ボタンタイプのフォーム要素でサポートされます。 ユーザが要素をクリックすると、このイベントが発生します。 onclick イベントハンドラが false を返すと、ボタンやリンクのデフォルトの処理をキャンセルします。例えば、 <a> タグの場合はハイパーリンクをたどらなくなります。また、submit ボタンの場合はフォームの送信を中止します。
- onmousedown, onmouseup
- この2つのイベントハンドラは onclick とほぼ同じですが、ユーザがマウスボタンをクリックしたときと、離したときで、 それぞれ別々にイベントが発生する点が異なります。ほぼ全ての要素でこれらのイベントハンドラはサポートされています。
- onmouseover, onmouseout
- この2つのイベントハンドラはそれぞれ、マウスカーソルがある要素の上に移動したときと、要素の上から離れたときに呼び出されます。
- onchange
- このイベントハンドラは <input>、<select>、<textarea> 要素でサポートされています。 要素中に表示されている値をユーザが変更し、Tabキーなどを使って要素からフォーカスを移動したときに、 このイベントハンドラが呼び出されます。
- onload
- このイベントハンドラが指定できるのは <body> タグ中のみ。ドキュメントや、画像などの外部コンテンツが 完全に読み込まれたときに、このイベントハンドラが呼び出されるます。onload イベントが発生したということは、 ドキュメントが完全に読み込まれ、状態が安定し、ドキュメントが変更可能になったということを意味します。
スクリプトの実行
1つのファイルに複数のスクリプトがある場合、ファイルに記述された順に実行されます。<script>タグ中の JavaScript コードは、 ドキュメントの読み込みや解析処理の一部として実行されます。
スクリプトは HTML ドキュメントの<head>部や<body>部にも記述できます。 <head>部のスクリプトでは、他の場所のコードから呼び出す関数の定義や、変数の宣言と初期化、onload イベントハンドラとして登録する関数の定義を記述するのが一般的です。
<body>部のスクリプトでも、<head>部のスクリプトと同じことができます。スクリプトが単に関数や変数を定義しているだけで、document.write() を呼び出したり、 そのほかの方法でドキュメントの内容を変更したりしないのであれば、<body>部ではなく、<head>部に 記述するようにします。
onload イベントハンドラ
ドキュメントの解析が完了し、全てのスクリプトが実行され、画像などの外部コンテンツが全て読み込まれると、 ブラウザは onload イベントを生成し、Window オブジェクトの onload イベントハンドラとして登録された JavaScript コードを実行します。
<body>タグの onload 属性を設定することで、onload イベントハンドラを登録できます。2つ以上のイベントハンドラが 登録されている場合は、ブラウザは全てのハンドラを呼び出しますすが、順序は保障されません。
onload イベントハンドラが呼び出されたときには、ドキュメントは完全に読み込まれ解析も完了し、 ドキュメントの任意の要素を JavaScript コードから操作できます。
onload イベントハンドラは、ドキュメントの解析が完了してから呼び出されるので、このイベントハンドラから document.writer() を呼び出してはいけません。もし呼び出すと現在のドキュメントを上書きしてしまいます。
onunload イベントハンドラ
ユーザがあるページから移動しようとするときに、ブラウザから onunload イベントハンドラが呼び出されます。このハンドラが、そのページに含まれる JavaScript コードを実行する最後の機会となります。
onunload イベントにより、onloadイベントハンドラ中のスクリプトなどで変更した内容を元に戻すことが可能です。 例えば、アプリケーションがサブウィンドウを開いた場合は、ユーザがページを移動するときに onunload イベントハンドラでウィンドウを閉じるなど。
また、時間のかかる処理やダイアログボックスの表示などは onunload イベントハンドラでは行わないようにする。
実行コンテキストとしてのWindowオブジェクト
ドキュメント中のスクリプトやイベントハンドラ、JavaScript URLは、グローバルオブジェクトとして、同一の Window オブジェクトを共有します。 また、JavaScript の変数や関数はグローバルオブジェクトのプロパティなので、ある<script>中で宣言された関数は、 後の<script>中のコードからも呼び出すことができます。
onload イベントが発生するのは、全てのスクリプトが実行された後なので、onload イベントハンドラから、ドキュメント中のスクリプトで 定義された全ての関数や変数にアクセスできます。
新しいドキュメントがウィンドウにロードされると、このウィンドウに対する Window オブジェクトはデフォルトの状態に戻され、ドキュメントは 完全にまっさらな状態に戻ります。これは、スクリプト中の変数や関数の寿命は、ドキュメントが新しいドキュメントで置き換わるまで、という意味でもあります。
Window オブジェクトのプロパティの寿命は、JavaScript を含んでいるドキュメントの寿命と同じですが、Window オブジェクト自身の寿命は もう少し長く、このオブジェクトが表すウィンドウが存在する限り、オブジェクトも存在します。
ブラウザウィンドウの制御
イベントドリブンモデル
ブラウザのページ上では、ボタンがクリックされたり、マウスがリンク上に乗ったりなど色々なイベントが発生します。クライアントサイド JavaScript ではそれらのイベントに対して実行するコードを記述します。このプログラミングモデルのことをイベント駆動型モデル(イベントドリブンモデル)と呼びます。
イベントに対して実行する処理を定義するコードのことをイベントハンドラと呼びます。
イベントとイベントハンドラの関連付け
クライアントサイド JavaScript ではイベントとイベントハンドラの関連付けを行う方法として、以下の方法があります。
- タグ内属性として宣言
- JavaScript コード内で宣言
- addEventListener/attachEvent メソッドを利用
タグ内属性として宣言
以下はボタンをクリックした際にアラートを表示する例です。
<input type="button" value="アラート表示" onClick="onclick_alert()" > <script type="text/javascript"> function onclick_alert() { window.alert("クリックされました"); } </script>
タグ内でイベントハンドラを宣言する場合は、以下のように記述します。イベント名はタグ内の属性として記述する場合は、大文字・小文字を区別しないので、onclick でも onClick でも大丈夫です。
<タグ名 onイベント名 = "JavaScriptコード">
JavaScriptコードにはイベントハンドラ(関数)を呼び出すためのコード(関数の呼び出し)を記述しますが、処理が単純な場合は、以下のように処理そのものを記述することもできます。
<input type="button" value="アラート表示" onClick="window.alert('クリックされました')" >
但し、タグ内に複雑なコードを記述することは避けるほうが良いでしょう。また、タグ内でスクリプトを記述する場合は、デフォルトのスクリプト言語を<head>内の<meta>タグで宣言するようにします。(HTML5では不要)
<meta http-equiv="Content-Script-Type" content="text/javascript" >
JavaScript コード内で宣言
HTML の中に JavaScript のコードを混在させることは、ページ構造とスクリプトの分離の観点から好ましくありません。JavaScript では、関連付けとイベントハンドラの宣言をまとめてコード内に記述することができます。以下は前述の例を書き換えたものです。input 要素に id 属性(dialog_btn)を追加し、onClick 属性を削除しています。
<input type="button" id="dialog_btn" value="アラート表示" > <script type="text/javascript"> window.onload = function() { document.getElementById('dialog_btn').onclick = function() { window.alert("クリックされました"); } }
window.onload で、ページロード時に実行するイベントハンドラを登録しています。
window.onload = function() { 処理(匿名関数・関数リテラルでイベントハンドラを登録) }
個別要素のイベントハンドラは、onload イベントハンドラの配下に記述します。onload イベントハンドラの配下に記述することで、文書全体が呼び出された後に getElementById() メソッドを実行するようにします。
window.onload = function() { document.getElementById('ID名').onイベント名 = function() { 処理(匿名関数・関数リテラルでイベントハンドラを登録) } }
タグ内で属性として記述する場合と異なり、イベント名は必ず全て小文字で指定する必要があります。
イベント標準の挙動のキャンセル
イベント標準の挙動(デフォルトの動作)とは、リンク(a 要素)がクリックされたら「ページへ移動する」、サブミットボタンがクリックされたら「フォームの内容を送信する」などの動作ですが、これらの挙動をイベントハンドラでキャンセルすることができます。
イベント標準の挙動(デフォルトの動作)をキャンセルするには、イベントハンドラで戻り値として false を返すようにします。
<a href="#" id="link_alert" title="アラート表示">アラート表示</a> window.onload = function() { document.getElementById('link_alert').onclick = function() { window.alert("クリックされました"); return false; //デフォルトの動作をキャンセル } }
直接タグに記述することもできます。
<div oncontextmenu="return false">コンテキストメニューを非表示</div>
(参考)return false を指定してもデフォルトの動作が実行されてしまう場合は、プラグイン(スムーズスクロール等)や他のスクリプトが影響している可能性があります。
Window オブジェクト
Window オブジェクトにアクセスする際には、自分自身を参照する winodw プロパティを省略することができます。例えば、以下は両方同じことになりますが、通常は window は省略します。
alert("hello"); //こちらでOK window.alert("hello");
また、Document オブジェクトの write メソッドにアクセスする場合でも直接 document プロパティを呼び出し write メソッドにアクセスできます。
document.write("hello"); //こちらでOK window.document.write("hello"); //冗長
ウィンドウのサイズと位置情報
大半のブラウザでは、Window オブジェクトのプロパティからウィンドウのサイズと位置情報を取得できます。 ただし、これらのプロパティ(Windowオブジェクトのプロパティ)をサポートしていないブラウザもあるので注意が必要です。
//ブラウザウィンドウの外枠の大きさ var windowWidth = window.outerWidth; var windowHeight = window.outerHeight; //デスクトップ上でのブラウザウィンドウの位置 var windowX = window.screenX; var windowY = window.screenY; //HTMLドキュメントの表示領域の大きさ=外枠の大きさからメニューバー、ツールバー、スクロールバーなどの大きさを引いた値 var vieportWidth = window.innerWidth; var vieportHeight = window.innerHeight; //以下の値でスクロールバーの水平位置と垂直位置を指定する var horizontalScroll = window.pageXOffset; var vertialcScroll = window.pageYOffset; //※これらの値は読み出し専用
ダイアログ表示・タイマー
Window オブジェクトは、ウィンドウ操作の機能の他にダイアログ表示やタイマー設定などの機能を提供します。
ダイアログ表示
Window オブジェクトでは、ダイアログを表示する alert, confirm, prompt というメソッドがあります。
これらのメソッドでダイアログを表示すると、ユーザは邪魔をされたと感じるので、現在ではこれらのメソッドが使われるのはデバッグのとき等です。
ダイアログボックスに表示される文字列は、HTML形式ではなくプレーンテキストで、レイアウトに使用できるのは、空白、改行、句読点記号のみになります。
これらのメソッドは、ダイアログボックスにユーザが応答するまでブロックされます。つまり、ダイアログボックスが表示された時点で、 JavaScript のコードは中断され、ユーザが応答すると、処理が再開されます。
- alert()
- メッセージを表示してユーザーに知らせ、ユーザがダイアログを閉じるまで待ちます。
- confirm()
- ユーザに確認を求め、ユーザは自分の操作を確認する場合は「OKボタン」を、取り消す場合は「Cancelボタン」をクリックします。confirm() は、以下の戻り値を返します。
- 「OKボタン」をクリックした場合: true
- 「Cancelボタン」をクリックした場合: false
<form onSubmit="return confirm('送信してもよろしいですか?');"> <input type="submit" value="送信"> </form>
- prompt()
- ユーザに文字列の入力を求めます。入力された文字列は戻り値として返されます。キャンセルされた場合は、null が返ります。
<input type="button" id="prompt_btn" value="入力ダイアログ表示" > <script type="text/javascript"> window.onload = function() { document.getElementById('prompt_btn').onclick = function() { var input = prompt("文字を入力してください。"); alert("入力文字: " + input); } } </script>
タイマー機能
「タイマー」を参照ください。
Screen オブジェクト
Window オブジェクトの screen プロパティには、Screen オブジェクトへの参照が格納されています。Screen オブジェクトには、画面サイズや色数など の情報を提供するプロパティがあります。
- width, height プロパティ
- 画面の表示サイズをピクセル単位で取得できます。
var screenWidth = screen.width; var screenHeight = screen.height;
- availWidth, availHeight プロパティ
- 利用可能な表示サイズを取得できます。利用可能な表示サイズとは、デスクトップのタスクバーなどの機能で 使用されるスペースを除いたサイズ。
var availWidth = screen.availWidth; var availHeight = screen.availHeight;
- availLeft, availTop プロパティ
- Firefox 系のブラウザでは、Screen オブジェクトに availLeft と availTop プロパティを定義しています。(IEではされていない) 画面上で利用可能な領域の左上隅の座標を表す。
var availLeft = screen.availLeft; var availTop = screen.availTop;
以下は実際に値を取得した例ですが、ブラウザにより値が異なっていることがわかります。
console.log(screen.width); //IE & FF: 1920, Chrome: 1739 console.log(screen.height); //IE & FF: 1080, Chrome: 979 console.log(screen.availWidth); //IE & FF: 1920, Chrome: 1739 console.log(screen.availHeight); //IE & FF: 1050, Chrome: 951 console.log(screen.availLeft); //IE: undefined, Chrome & FF: 0 console.log(screen.availTop); //IE: undefined, Chrome & FF: 0 console.log(screen.colorDepth); //24 (モニタの色数)
Location オブジェクト
ウィンドウやフレームの location プロパティには Location オブジェクトへの参照が格納されています。 Location オブジェクトは、ウィンドウに現在表示されているドキュメントの URL を表すプロパティ( URL についての情報)を持ち、 また、URL を変更するためのメソッドを提供をします 。
以下は、Location オブジェクトのプロパティです。戻り値の例は、現在の URL が以下の場合の結果です。
https://www.webdesignleaves.com:80/wp/?s=jquery#test
プロパティ | 説明 | 戻り値(例) |
---|---|---|
hash | # 記号に続く URL の部分(アンカー) | #test |
host | ホスト名とポート番号 | webdesignleaves.com:80 |
hostname | ホスト名(ポート番号を含まない) | webdesignleaves.com |
href | 完全な URL が文字列として格納されています | https://www.webdesignleaves.com:80/wp/?s=jquery#test |
pathname | パス(ホストからの相対。ファイル名を含む) | /wp |
port | URL のポート番号 | 80 |
protocol | URL のプロトコル | http: |
search | ? 記号に続くクエリ情報の部分。? 記号も含む | ?s=jquery |
https://www.webdesignleaves.com/jquery/jq_basic_12.html?s=jquery#test の場合の例
console.log(window.location.hash); //出力:#test console.log(window.location.host); //出力:www.webdesignleaves.com console.log(window.location.hostname); //出力:www.webdesignleaves.com console.log(window.location.href); //出力:"https://www.webdesignleaves.com/jquery/jq_basic_12.html?s=jquery#test" console.log(window.location.pathname); //出力:/jquery/jq_basic_12.html console.log(window.location.port); //出力なし(デフォルト) console.log(window.location.protocol); //出力:"http:" console.log(window.location.search); //出力:?s=jquery
以下は現在の URL のプロパティをコンソールに表示する関数の例です。
function showLocaction() { var x = window.location; var t = ['プロパティ - データ型 - 値', 'window.location - ' + (typeof x) + ' - ' + x ]; for (var prop in x){ t.push(prop + ' - ' + (typeof x[prop]) + ' - ' + (x[prop] || 'n/a')); } console.log(t.join('\n')); } showLocaction();
以下は表示例です。
"プロパティ - データ型 - 値 window.location - object - https://www.webdesignleaves.com/jquery/jq_basic_12.html?s=jquery#test href - string - https://www.webdesignleaves.com/jquery/jq_basic_12.html?s=jquery#test origin - string - https://www.webdesignleaves.com protocol - string - http: host - string - www.webdesignleaves.com hostname - string - www.webdesignleaves.com port - string - n/a pathname - string - /jquery/jq_basic_12.html search - string - ?s=jquery hash - string - #test assign - function - function assign() { [native code] } replace - function - function replace() { [native code] } reload - function - function reload() { [native code] } toString - function - function toString() { [native code] } valueOf - function - function valueOf() { [native code] } toJSON - undefined - n/a"
location.href
現在アクセスしている完全な URL を取得します。location オブジェクトの toString() メソッドは href プロパティの値を返すので、URL の文字を取得するのに location.href の代わりに location と記述することもできます。
また、指定した URL に遷移します。
location.href = 'http://google.com'; //http://google.com に移動
location.hash にアンカーを指定することもできます。
location.hash = "#test"; //#test にスクロール
ファイル名の取得
以下のような方法でファイル名を取得することができます。
var url = window.location.href; // 拡張子付き var filename = url.match(".+/(.+?)([\?#;].*)?$")[1]; // 拡張子無し var filename_no_ext = url.match(".+/(.+?)\.[a-z]+([\?#;].*)?$")[1];
または以下のように window.location.pathname でホスト以下のパスを取得し、それを split('/') で「/」区切りで配列に入れ、pop() で配列の最後の値を取得する方法もあります。
var file_name = location.pathname.split('/').pop(); var filename_no_ext = location.pathname.split('/').pop().replace(/\..+/, "");
window.location.pathname の代わりに、window.location.href を使うとアンカーや ? 記号に続く URL の部分があれば、それらも取得してしまいます。
var file_name_query = location.href.split('/').pop();
ディレクトリの取得
window.location.pathname を「/」で区切り、配列(dir_array)に格納します。さらに配列「dir_array」の、最後から数えて2番目のものを変数「dir_name」に格納します。
dir_array[dir_array.length -2] の「-2」を「-3」「-4」と変えることで任意の階層のフォルダパスを取得することができます。
var dir_array = window.location.pathname.split('/'); var dir_name = dir_array[dir_array.length -2]; console.log(dir_name);
また、以下のようにすると、ディレクトリのパスを取得できます。
var dir_array = window.location.pathname.split('/'); var dir_path = ""; for (var i = 0; i < dir_array.length -1; i++) { dir_path += dir_array[i] + '/'; } console.log(dir_path);
クエリ情報の取得
クエリ情報とは URL の末尾の ? 記号に続く部分で「キー名=値」で記述されています。この部分は、location.search で取得できます。以下は「キー:値」の形式で取得する関数の例です。また、値はエンコードされている可能性があるので、デコードします。
function getArgs(){ var args = new Object(); var query = location.search.substring(1); //検索文字列(疑問符以外)を取得 var pairs = query.split("&"); //&で分割 for(var i=0; i<pairs.length; i++){ var pos = pairs[i].indexOf('='); if(pos == -1)continue; //=がなければ飛ばす var argname = pairs[i].substring(0, pos); //名前を取り出す var value = pairs[i].substring(pos+1); //値を取り出す value = decodeURIComponent(value); //値をデコードする args[argname] = value; } return args; } var args = getArgs(); for(arg in args){ console.log(arg + " : " + args[arg], ""); } //URLがhttp://www.example.com/sample.html?name=Mike&age=32&country=USAの場合 //name : Mike //age : 32 //country : USA
以下は、Location オブジェクトのメソッドです。
メソッド | 説明 |
---|---|
assign(url) | 指定された URL の文書を読み込みます |
reload(forceget) | 現在の URL から文書を再読み込みします。forceget は、真偽値を取り、true の場合は、常にサーバーからページを再読み込みします。false、もしくは、指定されなかった場合、ブラウザは、自身のキャッシュからページを再読み込みする可能性があります |
replace(url) | 指定された URL に現在の文書を置き換えます。 assign() メソッドとの違いは、replace() を用いた後、現在のページは、セッションの履歴には保持されないことです。つまり、ユーザは、置き換え前のページに戻るために、戻るボタンを使うことができません |
toString() | Location オブジェクト URL を表す文字列を返します |
新しいドキュメントのロード
ウィンドウの location プロパティは、Location オブジェクトの参照を格納していますが、文字列を設定することもできます。 文字列を設定すると、ブラウザは文字列を URL として解釈し、その URL からドキュメントをロードして表示します。
if(!document.getElementById) location = "static.html"; //ブラウザが document.getElementById 関数をサポートしなければ、静的なページ(static.html)へ移動
Window オブジェクトにはブラウザに新しいページをロードして表示させるメソッドは存在しません。 新しいページをロードするには、ウィンドウの location プロパティに URL を設定します。また、Location オブジェクトにはこの機能に関連した reload(), replace() という2つのメソッドがあります。
- reload()
- 現在表示されているページを Web サーバから再び読み込みます。
- replace()
- 指定された URL をロードして表示します。但し、履歴リストに新しい項目を作成せずに、指定されたURLで現在の URL を置き換えます。 このため、「戻る」ボタンで戻れません。これに対して、location プロパティに URL を直接書き込む場合は、「戻る」ボタンで戻ることができます。
location プロパティは Window オブジェクトの location プロパティと Document オブジェクトの location プロパティの2種類あります。
- Window オブジェクトの location プロパティ
- Location オブジェクトへの参照が格納されています
- Document オブジェクトの location プロパティ
- 読み出し専用の文字列で、特別の機能はありません。document.location は document.URL と同じもの。 また通常、document.location は location.href と同じ値になりますが、ただし、サーバでリダイレクトされた場合、 document.location には実際にロードした URL が格納されますが、location.href には最初に要求した URL が格納されます。
History オブジェクト
Window オブジェクトの hisotry プロパティには、対応するウィンドウのページ履歴を管理する History オブジェクトが格納されています。 History オブジェクトで利用可能なメソッドは、back(), forward(), go() の3つがあります。
- back(), forward() メソッド
- ウィンドウやフレームの履歴上を戻ったり進んだりして、現在表示しているドキュメントを変更します。 (ブラウザの「戻る(Back)」ボタンや「進む(Forward)」ボタンと同じような動作)
- go()メソッド
- 整数値を引数として渡し、正の値の場合は進む方向に移動し、負の値の場合は戻る方向に移動します。
これらのメソッドを利用することで、ページ履歴上の前後のページに移動することができます。
<a href="JavaScript:history.back()">戻る</a> <a href="JavaScript:history.forward()">進む</a> <a href="JavaScript:history.go(-2)">2つ戻る</a>
上記は JavaScript 疑似プロトコル(JavaScript:~) を利用して記述していますが、onclick イベントハンドラを使って以下のようにも記述できます。
<a href="JavaScript:void(0)" onclick="history.back()">戻る</a>
onclick イベントハンドラを利用する場合、アンカータグのデフォルトの動作を抑止するために、void 演算子を使っています。(void 演算子は何も返さない演算子で、アンカータグの機能を抑止する場合等に使用します)
ウィンドウのオープンと制御
ウィンドウのオープン
新たにブラウザウィンドウをオープン(生成)するには Window オブジェクトの open() メソッドを使います。
open() メソッドを実行すると、新たにオープンされたウィンドウを表す Window オブジェクトが返されます。open() メソッドは引数を4つ指定できます。
var w = window.open(url, name, features);
- url
- 新しいウィンドウに表示する URL。新しいウィンドウにおいてフェッチしロードする URL を指定します。url が空文字列の場合は、メインウィンドウのデフォルトツールバーを持った新しい空白の空のウィンドウ(URL は about:blank がロードされます)が生成されます。 (省略可)
- name
- 新しいウィンドウの名前。名前には英数字と下線が使用可能。この名前は<form> HTML タグと<a> HTML タグの target 属性値として使われます。文字列の引数には一切空白を含むべきではありません。name は新しいウィンドウのタイトルを指定するものではありません。すでに存在しているウィンドウの名前を指定した場合、open() メソッドは新たにウィンドウを作らずに、指定されたウィンドウへの参照を返します。その場合、features 属性は無視されます。window.open() の全ての呼びだしで新しいウィンドウを開きたい場合、name に _blank という特別な値を用います。
- features
- ウィンドウのサイズや GUI の詳細情報を指定。省略するとデフォルトのサイズで、メニューバー、ステータス行、ツールバーなどが有効になった状態のウィンドウ(標準機能がすべて存在するウィンドウ)が表示されます。引数に値を指定すれば、どの機能を有効にするかを明示的に指定できます。 但し、その場合、明示的に指定されなかった機能は使われません。(下記参照)
features オプション
features オプションは「オプション名 = 値, ...」の形式で指定します。以下は指定可能なオプションの一部です。
オプション | 意味 |
---|---|
width | ウィンドウの横幅 |
height | windowの高さ |
location | アドレスバーを表示するかどうか(yes または no) |
scrollbars | スクロールバーを表示するかどうか(yes または no) |
resizable | ウィンドウのサイズを変更可能かどうか(yes または no) |
toolbar | ツールバー(戻る、進む等)を表示するかどうか(yes または no) |
status | ステータスバーを表示するかどうか(yes または no) |
menubar | メニューバーを表示するかどうか(yes または no) |
open() メソッドは、新たに生成されたウィンドウを表す Window オブジェクトを返すので、この Window オブジェクトを使って、新たに生成されたウィンドウを参照できます。
また、新たに生成されたウィンドウの JavaScript コードから、自分をオープンしたウィンドウを参照するには Window オブジェクトの opener プロパティを使います。なお、JavaScript コードでオープンしたのではなく、ユーザがウィンドウを オープンした場合、opener プロパティの値は null になります。
以下はボタンをクリックすると、新しいウィンドウをオープンする例です。
<button onclick="open_test_window()">Open</button>
function open_test_window() { w = window.open("http://webdesignleaves.com","test_window", "width=400,height=350,status=yes,resizable=yes"); }
第1引数の url に、JavaScript:擬似プロトコルを記述することもできます。
<button onclick="open_test_window2()">Open2</button>
function open_test_window2() { w = window.open("javascript:'<h1>Test Window</h1>'","test_window2", "width=400,height=350,status=yes,resizable=yes"); }
ウィンドウのクローズ
ウィンドウをクローズするには、close() メソッドを使用します。
生成したWindowオブジェクト(w)をクローズするにはw.close()とします。
var w = window.open(url, name, features, replace); w.close();
このウィンドウ内で実行されている JavaScript コードの場合、window.close(); のようにも記述できます。明示的に window を指定することで、 Document オブジェクトの close() メソッドではなく、Window オブジェクトの close() メソッドが呼び出されます。
通常、自動敵にウィンドウをクローズできるのは、JavaScriptコードで生成したウィンドウだけに限られます。これ以外のウィンドウを クローズしようとするとダイアログボックスが表示され、ユーザに確認が求められます。
ウィンドウがクローズした後でも Window オブジェクトはそのまま残りますが closed プロパティ以外のプロパティもメソッドも使えません。 closed プロパティはウィンドウがクローズされたときに true になります。closed プロパティを調べることで、そのウィンドウが閉じられているかどうかを確認することができます。
ウィンドウの移動とサイズの変更
Window オブジェクトにはウィンドウを移動したり、サイズを変更するメソッドが定義されています。但し、これらに関してはユーザ自身が制御したいはずだから一般的にはこれらのメソッドは使わないほうが無難かも知れません。
メソッド | 意味 |
---|---|
moveTo(x,y) | 指定された座標値へウィンドウの左上隅を移動(絶対座標) |
moveBy(x,y) | 指定されたピクセル数だけウィンドウを上下左右に移動(相対座標) |
resizeTo(x,y) | 絶対値を指定して、ウィンドウのサイズを変更(絶対サイズ) |
resizeBy(x,y) | 相対値を指定して、ウィンドウのサイズを変更 (相対サイズ) |
キーボードフォーカスとウィンドウ表示
ウィンドウにキーボードフォーカスを与えるには、focus() メソッドを使い、キーボードフォーカスを外すには blur() メソッドを使用します。
メソッド | 意味 |
---|---|
focus() | ウィンドウにキーボードフォーカスを与えます。 |
blur() | ウィンドウからキーボードフォーカスを外します。 |
focus() メソッドを使うと、ウィンドウがほかのウィンドウの下に隠れているような場合でもウィンドウが前面に表示され、 ユーザから見えるようになります。Window.open() を使って新しいウィンドウをオープンするときは、ブラウザが自動的にこのウィンドウを最前面に表示しますが、2番目の引数に既存のウィンドウ名を指定した場合、open() メソッドは自動的にユーザから見えるようにはしないので、open() でオープンした後に focus() で呼び出します。
スクロール
Windows オブジェクトには、ドキュメントをスクロールするメソッドがいくつかあります。
メソッド | 意味 |
---|---|
scrollBy(x,y) | ウィンドウに表示されたドキュメントを、指定されたピクセル数だけ上下左右にスクロールします。(相対位置) |
scrollTo(x,y) | ウィンドウに表示されたドキュメントを、指定した位置までスクロールします。(絶対位置) |
最近のブラウザでは、ドキュメントの HTML 要素が offsetLeft と offsetTop というプロパティを持っています。このプロパティには、要素の X,Y 座標が 格納されいるので、要素の位置がわかれば、scrollTo() を使ってある要素がウィンドウの左上隅に表示されるようにスクロールできます。但し、offsetLeft と offsetTop の値はブラウザにより異なるので、実用的には難しいかも知れません。
var target = document.getElementById("window_close"); scrollTo(target.offsetLeft, target.offsetTop);
- scrollIntoView()
- 最近のブラウザは、scrollIntoView()というメソッドをサポート。任意のHTML要素に対して scrollIntoView() を呼び出すと、 要素が見える位置にスクロールされる。
scrollIntoView() は focus() ほど多くのブラウザでサポートされているわけではありませんが、 focus() とは異なり、キーボードフォーカスを受け付ける要素だけでなく、任意の HTML 要素に対して利用できます。
element.scrollIntoView(alignWithTop);
パラメータ alignWithTop Optional:
- true を指定した場合、要素が上端に来るようにスクロールする(デフォルト)
- false を指定した場合、下端に来るようにスクロールする。
function showIt(elID) { var el = document.getElementById(elID); el.scrollIntoView(true); }
ドキュメント中のスクロールしたい場所に <a name= > タグを使ってアンカーを定義してウィンドウをスクロールする方法もあります。 定義したアンカー名を Location オブジェクトの hash プロパティに指定すれば、その場所にスクロールすることができます。
ドキュメントの先頭に「top」という名前のアンカーを定義しておくと、次のようなスクリプトで先頭に戻ることができます。
window.location.hash = "#top";
この場合、ブラウザのロケーションバーにドキュメントの現在位置が表示され、ブックマークすることも可能です。また、Backボタンを使って直前の位置に戻ることができます。
ブラウザ履歴に残したくない場合は replace() を使って以下のようにすれば、ブラウザ履歴を残すことなくアンカーにスクロールできます。
window.location.replace("#top");
Window オブジェクトのメソッドの使用例
以下は、O'Reilly JavaScritp(第5版)に紹介されているサンプルです。
var bounce = { x:0, y:0, w:300, h:300, //ウィンドウの位置とサイズ dx:10, dy:10, //移動距離 interval:200, //更新間隔(ミリ秒) win: null, //生成するウィンドウ timer:null, //setInterval() の戻り値 //アニメーション開始の関数 start: function(){ //ウィンドウの初期位置を画面中央に bounce.x = (screen.width - bounce.w)/2; bounce.y = (screen.height - bounce.h)/2; //画面を移動するウィンドウを生成 bounce.win = window.open('javascript:"<h1>Bounce!</h1>"',"", "left=" + bounce.x + ", top=" + bounce.y + ",width=" + bounce.w + ",height=" + bounce.h + ",status = yes"); bounce.timer = setInterval(bounce.nextFrame, bounce.interval); }, //アニメーション停止の関数 stop: function() { clearInterval(bounce.timer); if(!bounce.win.closed) bounce.win.close(); }, //アニメーションの動き方 nextFrame: function(){ //closed プロパティが true であれば(ウィンドウが閉じられていれば)停止 if(bounce.win.closed){ clearInterval(bounce.timer); return; } //画面の左右の端に達したら方向を逆転 if((bounce.x + bounce.dx > (screen.availWidth - bounce.w)) || (bounce.x + bounce.dx < 0))bounce.dx = -bounce.dx; //画面の上下の端に達したら方向を逆転 if((bounce.y + bounce.dy > (screen.availHeight - bounce.h)) || (bounce.y + bounce.dy < 0)) bounce.dy = - bounce.dy; bounce.x += bounce.dx; bounce.y += bounce.dy; bounce.win.moveTo(bounce.x, bounce.y); bounce.win.defaultStatus = "(" + bounce.x + "," + bounce.y + ")"; } }
ドキュメントの制御
Document Object Model (DOM)
全てのブラウザウィンドウ(やフレーム)は HTML ドキュメントを表示します。このウィンドウを表す Window オブジェクトには document プロパティがあり、 この document プロパティは Document オブジェクトを参照します。
クライアントサイドオブジェクトの階層構造のうち、Document オブジェクトから下位の階層のことをドキュメントオブジェクトモデル(DOM)と呼びます。Document オブジェクトは、主なブラウザで実装されていて事実上の標準となっています。
DOM(Document Object Model)は、ドキュメントを構成するオブジェクトにアクセスする API (Application Programming Interface) です。DOM には以下のようなものがあります。
- DOM Level 0(レガシー DOM)
- DOM Level 1(W3C DOM)
- DOM Level 2(W3C DOM)
- DOM Level 3(W3C DOM)
最初に制定されたのが DOM Level 0 で、現在では W3C 標準に組み入れられ、全てのブラウザでサポートされています。DOM Level 0 はレガシー DOM とも呼ばれます。DOM Level 0 で定義されているのは、Document クラス(以下では Document オブジェクトと呼んでいます)だけです。
W3C DOM では Document API が定義されていて、これにはドキュメントに関連した汎用的な機能が提供されています。また、HTML に特化したプロパティやメソッドが追加された HTML Document API も提供されています。
ドキュメントコンテンツの動的な生成
Document オブジェクトの write() メソッド(または writeln() メソッド:改行付きの書き出し)を使うと、ドキュメントにコンテンツを記述できます。write() メソッドはレガシー DOM にも含まれていて、JavaScript のリリース当初から使われてきています。
write()メソッドを使って現在のドキュメントにHTMLを出力できるのは、ドキュメントが解析中の間だけです。document.write( )の呼び出しを行った <script> タグの場所にテキストが挿入されます。
関数中で document.write() を 呼び出すように定義し、イベントハンドラからこの関数を呼び出した場合、現在のドキュメントが消去され、このドキュメントに含まれていた スクリプト自体も消去されるので注意が必要です。
これは、ページ出力が完了した状態で write()/writeln() メソッドを呼び出すと、新しいドキュメントをオープンするため、元々出力されていたドキュメントがクリアされてしまうためです。
<script type="text/javascript"> var today = new Date(); document.write("<p>Document accessed on : " + today.toString() + "</p>"); </script>
write()メソッドをDocument オブジェクトの open(), close() と組み合わせる方法
write() メソッドを open(), close() と組み合わせると、他のウィンドウやフレームに新しいドキュメントを作成することができます。
<button onclick="hello_window()">新しいウィンドウを表示</button>
function hello_window() { var w = window.open(); //コンテンツがない新しいウィンドウを生成 var d = w.document; //このウィンドウのDocumentオブジェクトを取得 d.open(); //新しいドキュメントをオープン(省略可) d.write("<h1>Hello world!</h1><p>New Window</p>"); //ドキュメントのコンテンツを出力 d.close(); //ドキュメントを閉じる(ドキュメントの作成を完了) }
close() メソッドは必須です。close() メソッドを呼び出してドキュメントを明示的に完了して初めて、バッファされたコンテンツが画面に表示されます。 (忘れた場合、ブラウザのドキュメントロード中のアニメーションが止まらず、また記述した HTML もバッファ中に保存されているだけになります。)
これに対し、open() メソッドは省略可能です。すでにクローズされたドキュメントに対して write() メソッドを呼び出せば、open() メソッドを 呼び出したときと同じように、JavaScript が新しい HTML ドキュメントをオープンします。
イベントハンドラから document.write() を 呼び出した場合にも、同じことが行われ JavaScript が新しい HTML ドキュメントをオープンします。しかし、この処理の中で、 現在のドキュメントは、コンテンツやスクリプト、イベントハンドラも含めて全て捨てられるのでイベントハンドラから自分自身の ドキュメントに対して write() メソッドを呼び出さないようにします。
write()メソッドに複数の引数を渡す
write()/writeln() メソッドには複数の引数(文字列)を渡すことができます。カンマ演算子で指定された文字列は、記述した順に出力されます。以下は同じことになります。
var greeting = "Hello "; var welcome = "Welcome to my Web!"; document.writeln(greeting, welcome); document.writeln(greeting + welcome);
Document オブジェクト(DOM Level 0)のプロパティ
以下は Document オブジェクト(DOM Level 0)のレガシーなプロパティです。
プロパティ | 概要 |
---|---|
bgColor | ドキュメントの背景色。<body>タグの bgcolor プロパティに対応(現在は非推奨) |
cookie | HTTPクッキーを読み書きをするプロパティ |
domain | 同一出身ポリシー制限を緩和するためのプロパティ。このプロパティを使うことで、 複数のWebサーバが連携したサイトを作成できる。 |
lastModified | ドキュメントの最終更新日時を表す文字列 |
location | URLプロパティと同じ(このlocationプロパティの使用は推薦されていない) |
referrer | ブラウザが現在のドキュメントをロードすることになったリンクを含んでいるドキュメントのURL(もしあれば)。 つまり、ユーザが現在のドキュメントを見ることになったリンクを含むURLが格納されている。 |
title | このドキュメントの<title>タグと</title>タグの間にあるテキスト |
URL | ロードされたドキュメントの URL を表す文字列。サーバがリダイレクトをした場合を除き、 Window オブジェクトのl ocation.href プロパティと同じ値になる。 |
これらのプロパティは、ドキュメントに関する情報を提供します。以下はドキュメントのタイトル、URL、最終更新日時を表示する例です。
<i>Document:<script>document.write(document.title);</script></i><br> <i>URL: <script>document.write(document.URL);</script></i><br> <i>Last Update:<script>document.write(document.lastModified);</script></i>
URL:
Last Update:
関連ページ:ページの最終更新日を表示
レガシーDOM:Documentオブジェクトの配列プロパティ
以下はレガシー DOM の、オブジェクトの配列を値として持つプロパティです。これらのプロパティを使うことで、ドキュメントの要素にアクセスすることができます。これらのプロパティは、ドキュメントに現れるリンクや画像、フォームなどの配列を表します。
各配列の要素は、ドキュメントのソースコード中に現れるのと同じ順序で格納されます。これらのレガシー DOM の配列に含まれるオブジェクトは、 スクリプトから操作できます(リンクの飛び先を取得したり変更したり、フォーム要素の値を読み書きしたりできる)。しかし、ドキュメントのテキストは変更できません。(ドキュメントの構造自体は変更できない)
- anchors[]
- ドキュメントのアンカーを示す Anchor オブジェクトの配列。(アンカーは、<a>タグで href 属性の代わりに name 属性を指定することで作成される。ハイパーテキストとは異なる。)Anchor オブジェクトの name プロパティに name 属性の値が格納される。
- applets[]
- Javaアプレットを表す Applet オブジェクトの配列。
- forms[]
- <form>要素に対応する Form オブジェクトの配列。各 Form オブジェクトには、elements[] という、フォーム要素に対応するオブジェクトが格納されている配列プロパティを持つ。フォームが送信される前に onsubmit イベントハンドラが呼び出される。false を返せば、ブラウザはフォームの送信を取りやめる。このイベントハンドラを使って、クライアントサイドでのフォームの検証が行える。
- images[]
- <img>要素に対応する Image オブジェクトの配列。Image オブジェクトの src プロパティは読み書きが可能。このプロパティに URL を設定すると、ブラウザは新しい画像をロードして表示する。(古いブラウザでは新しい画像は元の画像と同じサイズでなければならない。)Imgae オブジェクトの src 属性を制御すればロールオーバーや簡単なアニメーションが実現できる。
- links[]
- ドキュメント内のハイパーテキストリンクを表す Link オブジェクト。ハイパーテキストリンクは<a>タグを使って作成される。(<area>タグを使って作成される場合もある。)Link オブジェクトの href プロパティは<a>タグの href 属性に対応し、リンクの URL が格納される。また、Link オブジェクトでは、protocol や hostname、pathname などのプロパティを使って URL の各部を取り出せる。(location オブジェクトに似ている。)また、マウスカーソルがリンク上に移動した場合、onmouseover イベントハンドラが呼び出され、リンクから外れたら onmouseout イベントハンドラが呼び出される。このイベントハンドラが false を返した場合、ブラウザはリンクをたどらない。
ドキュメントのオブジェクトへの名前付け
数値でインデックス付けされたオブジェクトの配列で問題になるのは、ドキュメント要素の順番を少し変更しただけで、その順序に依存したコードは動かなくなってしまうことです。もっと確実なコードを書くには、ドキュメント要素に名前を付けて、この名前を使って要素を参照するようにします。レガシー DOM で名前をつけるには、要素の name 属性を使います。
次のようなフォームが指定されている場合、この<form>がドキュメントの最初のものだとすると、Formオブジェクトにアクセスするには以下のの3つの方法があります。
<form name ="fm1"> <input type="button" value="Send"> </form>
- document.forms[0] //フォームのドキュメント中の順序で参照
- document.forms.fm1 //プロパティとしてフォームの名前で参照
- document.forms["fm1"] //配列のインデックスとしてフォームの名前で参照
また、<form>、<img>、<applet>、タグに name 属性を指定した場合は、ドキュメントオブジェクト自身に name 属性で指定した名前のプロパティができ、対応する Form、Imgae、Applet オブジェクトが格納されます。(但し<a>タグの場合はこのようにならない)
つまり、先ほどのフォームには次のように記述してもアクセスできます。
document.fm1
また、フォーム中の要素にも名前が付けられます。フォーム要素に name 属性を指定すると、Form オブジェクトに name 属性に指定した名前でプロパティができ、フォーム要素を表すオブジェクトを参照できます。
<form name="shipping"> <input type="text" name="zip"> </form>
上記の場合、このフォームのテキスト入力要素へは次のような書式で参照できます。
document.shipping.zip
2つのドキュメントの要素の name 属性に同じ値を設定すると、その名前のプロパティは配列となり、両方の要素を表すオブジェクトの参照が格納されます。HTML のフォームでは、関連するラジオボタンやチェックボックスには同じ名前が付けられます。このような場合、Form オブジェクトにはその名前のプロパティが作成され、その値は配列になり、各オブジェクトへの参照が格納されます。
レガシー DOM の例
以下は、新しいウィンドウをオープンして、document.write() を使って画像のリストを出力する例です。関数の中で window.open() を使っていますが、ポップアップウィンドウは、ユーザーの操作で作成されたものでなければ、ブラウザによってブロックされます。そのため、この関数はリンクやボタンをクリックした時に呼び出すようにします。
<button onClick="list_images();">画像リストの表示</button> <script type="text/javascript"> function list_images() { //現在のウィンドウのドキュメント var d = window.document; //新規ウィンドウをオープン var nw = window.open( "", "_blank", "menubar=yes,scrollbars=yes,location=yes,resizable=yes, toolbar=yes, width=500, height=300" ); //新規ウィンドウ(のドキュメント)にタイトルを表示 nw.document.write("<h1>List of images: " + d.title + "</h1>"); //全ての画像のソース属性と alt 属性を使ってリンクを作成 for(var i = 0; i < d.images.length; i++) { var img = d.images[i]; nw.document.write('<a href="' + img.src + '" >' + (i + 1) + "番目の画像: "+ img.alt + '</a><br>'); } //close() メソッドでドキュメントを明示的に完了 nw.document.close(); } </script>
W3C DOM
W3C DOM はレガシー DOM より機能が拡張されていて、標準化もされています。
DOM は HTML や XML などのマークアップ言語で書かれたドキュメントにアクセスするための標準的な仕組みです。
HTML ドキュメントは入れ子になったタグによる階層構造を持ちます。DOM はドキュメントをドキュメントツリー(文書ツリー)として扱います。
<html> <head> <title>Sample Document</title> </head> <body> <h1>An HTML Document</h1> <p>This is a <i>simple</i> document</p> </body> </html>
上記 HTML ドキュメントを DOM で表現するとノードで構成される次のようなツリー構造になります。 (文書ツリーの解釈はブラウザやそのバージョンによって微妙に異なります)
Document | <html> | -------------------------------- | | <head> <body> | | <title> ------------------------ | | | "Sample Document" <h1> <p> | | "An HTML Document" -------------------- | | | "This is a" <i> "document" | "simple"
文書を構成する要素や属性、テキストと言ったオブジェクトのことをノードと呼びます。オブジェクトの種類により、要素ノード、属性ノード、テキストノードなどと呼びます。
DOM はこれらのノードを抽出、追加、削除、置換するための手段を提供する API (Application Programming Interface) です。
ノード
Node インターフェース(※)には、ツリー構造を探索したり操作したりするためのプロパティやメソッドが定義されています。 Node オブジェクトの childNodes プロパティには、子ノードのリストを保持します。
また、firstChild, lastChild, nextSibling, previousSibling, parentNode プロパティを使って、ノードのツリー構造を探索することができます。appendChild(), removeChild(), replaceChild(), insertBefore() などを使って、ドキュメントのツリー構造にノードを追加したり削除したりすることもできます。
※ DOM 標準仕様では、クラスではなくインターフェースを定義しています。インターフェースは実装しなければならないメソッドやプロパティをまとめたものです。
ノードの種類
ドキュメント中で異なる種類のノードは、Node のサブインターフェースとして表されます。全ての Node オブジェクトは nodeType プロパティを持ち、 このプロパティにはノードの種類が格納されます。
例えば、ノードの nodeType プロパティの値が Node.ELEMENT_NODE と等しい場合は、 この Node オブジェクトは Element オブジェクトでもあり、Element インターフェースで定義されるメソッドやプロパティが使用できます。
ノード | インターフェース | nodeType 定数 | nodeType の値 |
---|---|---|---|
要素ノード | Element | Node.ELEMENT_NODE | 1 |
属性ノード | Attr | Node.ATTRIBUTE_NODE | 2 |
テキストノード | Text | Node.TEXT_NODE | 3 |
コメントノード | Coment | Node.COMMENT_NODE | 8 |
文書ノード | Document | Node.DOCUMENT_NODE | 9 |
DOM ツリー構造のルートにあたるノードが Document オブジェクトになります。このオブジェクトの documentElement プロパティが、ドキュメントのルート要素を表す Element オブジェクトを参照します。HTML ドキュメントの場合はこれは <html> タグになります。
DOM ツリーには Document オブジェクトは1つしか存在しません。ツリーの大部分のノードは <html> や <i> などのタグを表す Element オブジェクトと、 テキスト文字列を表す Text オブジェクトです。ドキュメントパーサがコメントを削除しない場合、コメントは Comment オブジェクトで表されます。
以下は、コア DOM インターフェースのクラス階層の抜粋です。
- document.documentElement → htmlエレメントの取得 <html>タグ
- document.body → bodyエレメントの取得 <body>タグ
- document.head → headエレメントの取得 <head>タグ
- document.childNodes → 子ノードのリスト
- document.childNodes[1] → htmlエレメントの取得 <html>タグ
- document.childNodes.item(1) → htmlエレメントの取得 <html>タグ
- document.getElementsByTagName("html")[0]) → htmlエレメントの取得 <html>タグ
- document.getElementsByTagName("body")[0]) → bodyエレメントの取得 <body>タグ
属性
HTML タグの標準的な属性は、そのタグを表す Element ノードのプロパティに保持されます。 Element インターフェースの getAttribur(), setAttribute(), removeAttribute() メソッドを使って、要素の属性を取得、設定、削除することができます。
DOM HTML API
DOM 標準は、XML ドキュメントと HTML ドキュメントの両方で使われることを意図して設計され、Node や Element、Document などのコア DOM API は 汎用的に定義され、両方のドキュメントで利用できます。
- HTMLDocument は Document の HTML に特化したサブインターフェース。
- HTMLElement は Element の HTML に特化したサブインターフェース。
さらに、DOM は、多くの HTML 要素に対して、そのタグに特化したインターフェースを定義しています。
HTMLBodyElement や HTMLTitleElement などの タグ特有のインターフェースは、その HTML タグの属性を表すプロパティを定義しています。
HTMLElement インターフェースには、id, style, title, lang, dir, className プロパティが定義されていて、これらのプロパティを使って 全ての HTML タグで利用可能な id, style, title, land, dir, class 属性にアクセスできます。 (HTML の class 属性は JavaScript では className プロパティに名前が変更されています)
HTML 標準に含まれない属性を操作する場合は getAttribute() と setAttribute() を使います。
HTML 命名規則
HTML は大文字と小文字を区別しませんが、JavaScript は大文字と小文字を区別します。
そのため、HTML インターフェースのプロパティは小文字で記述します。但しプロパティ名が複数の単語から成る場合は、2語目以降の単語の先頭の文字を大文字で記述します。input タグの maxlength 属性は、HTMLInputElement では、maxLength プロパティになります。
例外としては、class 属性は、HTMLElement では className プロパティになります。
ドキュメントの探索
DOM は HTML ドキュメントを Node オブジェクトのツリー構造で表します。以下は、あるノードとその子ノードを再帰的に探索して、HTML タグ(Element Node)の数をカウントする関数です。
ノードの childNodes プロパティの値は NodeList オブジェクトで、このオブジェクトは Node オブジェクトの配列のように振舞います。そのため childNodes[] 配列の各要素をループで調べることで、あるノードの全ての子ノードを調べることができます。
各ノードの種類を判定するには、nodeType プロパティの値を使っています。
また、この関数を呼び出すのは、ドキュメントが完全に読み込まれてからにする必要があります。必要に応じて onload イベントハンドラから呼び出します。これは DOM を使う場合の必要条件で、ドキュメントが完全に読み込まれるまでは、ドキュメントのツリー構造を探索したり操作したりできません。
<script type="text/javascript"> function countTags(n){ var numTags = 0; if(n.nodeType == 1) //Node.ELEMENT_NODEの値(1)nがElementかチェック numTags++; //そうであればカウンタを増やす var children = n.childNodes; //nの全ての子ノードを取得 for(var i=0; i < children.length; i++){ //子ノードをループで調べる numTags += countTags(children[i]); //再帰的に処理 } return numTags; } </script> <button onClick="alert('このページのタグの総数:' + countTags(document));">
以下は、あるノード以下の Text ノードを全て検索し、ノードのテキストを1つの文字列にまとめて返す関数の例です。
firstChild, lastChild はそれぞれノードの最初の子ノードと最後の子ノードを参照するプロパティです。nextSibling, previousSibling はそのノードの兄弟ノードを参照するプロパティです。
文字列の結合を何度の繰り返すのは効率が悪いので、それぞれのノードのテキストを配列に格納して最後に結合しています。
function getText(n){ var strings = []; getStrings(n, strings); return strings.join(""); function getStrings(n, strings){ //if(n.nodeType == 9) n = n.documentElement; if(n.nodeType == 3 /*Node.TEXT_NODE */) strings.push(n.data); else if(n.nodeType == 1 /*Node.ELEMENT_NODE */){ for(var m = n.firstChild; m != null ; m = m.nextSibling){ getStrings(m, strings); } } } }
以下は上記関数の呼び出しの例です。
<body onload="alert('この文書のテキスト:' + getText(document.documentElement) )">
引数に単に document を渡すと document の nodeType は 9 なので探索できないため、document を渡せるようにするには if(n.nodeType == 9) n = n.documentElement; のコメントを外す必要があります。
body 要素のテキストだけを探索するには引数に document.body を,head 要素を探索するには document.head 渡します。
ノードの検索・取得
ドキュメントからテキストを取得したり、特定の要素を操作したりするには、目的の要素を取得する必要があります。その方法としては大きく分けると以下の3つがあります。
- ID やタグ名から目的の要素を取得する方法(ダイレクトアクセス)
- CSS のセレクタの書式で指定された要素を取得する方法
- ある要素を基点にその子要素、親要素、兄弟要素などの相対的な位置関係から取得する方法(ノードウォーキング)
getElementByID()
getElementByID() は指定された ID を持つ要素を返します。id 属性の値は一意なので、getElementByID() は id 属性が一致した要素を1つだけ返します(要素の配列は返さない)。
element = document.getElementById(id);
- element
- Element オブジェクト。
もし与えられた ID を持つ要素がなければ、この関数は null を返します。 - id
- 検索する要素の一意の ID を表す文字列
ドキュメントのある特定の要素を操作する場合は、その要素の id 属性にドキュメント内で一意な名前(id 名)を設定しておきます。
以下は、id 属性が「element_id」の要素を変数 elem に取得して、そのタグの内容をアラートで表示する例です。innerHTML は、要素の内容を取得・設定するためのプロパティです。
<p id="element_id">この内容を表示</p> <script type="text/javascript"> var elem = document.getElementById('element_id'); alert(elem.innerHTML); </script>
また、単純に要素を生成して id 属性を設定しただけでは、getElementById によって要素にアクセスすることはできません。その前に、Node.insertBefore() や同種のメソッドを使って、文書ツリー内に要素を挿入する必要があります。
var elm = document.createElement("div"); // 要素を生成 elm.id = 'test_elem'; // 生成した div 要素の id を設定 alert( document.getElementById('test_elem') ); // null document.body.appendChild(elm); alert( document.getElementById('test_elem') ); // [object HTMLDivElement]
getElementsByTagName()
document.getElementsByTagName() は指定したタグ名の要素を全て取得して返します。ルートノードを含めたドキュメント全体が検索されます。
また、Element オブジェクトにも getElementsByTagName() メソッドが定義されています(後述)。
elements = document.getElementsByTagName(name)
- elements
- ドキュメントに現れる順に並べられた、見つかった要素の配列(正確には、NodeList オブジェクト:配列のようなオブジェクト)
- name
- 要素の名前を表す文字列。特殊な文字列 "*" は全ての要素を表します。HTML は大文字・小文字を区別しないので、ここで指定する文字列も大文字・小文字を区別しません。
以下やドキュメント中の全ての table 要素を取得して、その数を表示する例です。
var tables = document.getElementsByTagName('table'); alert("表の総数: " + tables.length);
HTMLDocument の body プロパティ(document.body)は、<body>タグを参照しますが、このプロパティが存在しない場合に、<body>タグを参照するには以下のようなコードを使用します。
document.getElementsByTagName("body")[0]; //bodyは1つしか存在しないので最初の要素を使用。
以下はドキュメント内の全ての a 要素を取得して、そのリンク先(href 属性)を表示する例です。
<button onClick="show_all_href();">表示</button> <script type="text/javascript"> function show_all_href() { var a = document.getElementsByTagName("a"); var links = []; for (var i = 0 ; i < a.length; i ++) { links.push(a.item(i).href); } alert(links.join("\n")); } </script>
Element オブジェクトの getElementsByTagName() は、Document オブジェクトのものと同じような働きをしますが、呼び出した要素の子要素だけを返します。ドキュメント全体から検索するのではなく、ある要素の中から検索します。
以下は、getElementByID() で特定の要素を検索し、次に Element オブジェクトの getElementsByTagName() でその要素の子孫の中からある種類の要素を全て検索する例です。
window.onload = function() { //id 属性が「foo」という要素を取得 var foo = document.getElementById('foo'); //「foo」という要素の子孫の中から全ての a 要素を取得 var links = foo.getElementsByTagName('a'); alert(links.length); }
getElementsByName()
getElementsByName() は、ドキュメント内の指定した name 属性値を持つノードリストを返します。name 属性はドキュメント内で一意とは限らないので(フォームのラジオボタンやチェックボックス等)、getElementsByName() は要素の配列(NodeList オブジェクト)を返します。
elements = document.getElementsByName(name)
- elements
- NodeList オブジェクト
- name
- 要素の name 属性の値
<form name="form1"><input type="text"></form> <script> var form1 = document.getElementsByName("form1"); console.log(form1[0].tagName); // FORM </script>
getElementsByName() は、与えた name 属性の値を持つすべての要素を含む、現在の NodeList コレクションを返します。<meta> や <object>、または name 属性を要素内に置くことがサポートされていないものも name 属性が付いていれば含まれます。
getElementsByName() メソッドは、ブラウザごとに動作が異なります。IE や Opera では、getElementsByName() メソッドは、指定した値の id 属性を持つ要素も返します。そのため、name と ID に同じ文字列を使用しないように、よく注意する必要があります。
getElementsByClassName()
getElementsByClassName() は、指定されたクラス名で得られる要素の集合を返します。document オブジェクトで呼び出されたときは、ルートノードを含む、完全な文書が検索されます。任意の要素で getElementsByClassName を呼び出すこともできます。その場合は、与えられたクラス名を持つ指定された要素下の要素だけが返ります。
elements = document.getElementsByClassName(name) // または elements = rootElement.getElementsByClassName(name)
- elements
- 現在の NodeList で、要素は、それらがその文書ツリーに現れる順番に得られます。
- name
- 要素のクラス名を表す文字列。複数のクラス名を指定する場合は、空白区切りでクラス名を指定します。
getElementsByClassName() は、document だけでなく、任意の要素で呼び出すことができます。呼び出した要素が検索のルートとして使われます。
以下は、'foo' クラスを持っている全ての要素を取得する例です。
document.getElementsByClassName('foo')
以下は、'foo' クラスと 'bar' クラスを持っている全ての要素を取得する例です。
document.getElementsByClassName('foo bar')
以下は、'foo' ID を持っている要素内で、'bar' クラスを持っている全ての要素を取得する例です。
document.getElementById('foo').getElementsByClassName('bar')
querySelector()
querySelector() は CSS のセレクタの書式で指定された要素中の文書内で一番初めにある要素1つを取得します。CSS のセレクタの書式で指定できるので、jQuery に少し似た感じで操作できます。
querySelector() は指定された要素を1つ見つけるとそこで検索処理を終了します。そのため対象の要素が複数あっても最初の1つしか取得しません。
一致するものが見つからない場合は null を返します。document.getElementById() や document.getElementsByClassName() と比べると処理速度は遅くなります。
IE は 8から対応しています(完全対応は9から)。 caniuse querySelector
以下のような HTML がある場合をみてみます。
<div id="foo"> <ul> <li>abc</li> <li class="bar">def</li> <li>ghi</li> <li>jkl</li> </ul> <ul> <li>mno</li> <li>pqr</li> <li>stu</li> <li class="bar">vwx</li> </ul> </div>
以下のように指定すると、クラスが bar の最初の li 要素のみが取得され、style.color = "red" で文字色が赤になります。
var el = document.querySelector('#foo li.bar'); el.style.color = "red";
- abc
- ghi
- jkl
- mno
- pqr
- stu
querySelectorAll()
querySelectorAll() はセレクタで指定された要素の全てを配列として取得します。文書内で該当する要素を検出した順に配列(正確には静的な NodeList)に格納して返します。querySelector() と同様に、CSS のセレクタの書式で指定できます。
戻り値の配列が空である(length プロパティが 0 である)場合は、該当する要素が無かったということになります。document.getElementById() や document.getElementsByClassName() と比べると処理速度は遅くなります。
IE は 8から対応しています(完全対応は9から)。 caniuse querySelectorAll
以下のような HTML がある場合をみてみます。
<div id="foo"> <ul> <li>abc</li> <li>def</li> <li class="bar">ghi</li> <li>jkl</li> </ul> <ul> <li>mno</li> <li>pqr</li> <li class="bar">stu</li> <li>vwx</li> </ul> </div>
以下のように指定すると、クラスが bar の li 要素全てが取得されます。配列なので for 文などを使って処理をします。この例の場合、2つの li 要素が style.color = "green" で文字色が緑になります。
var els = document.querySelectorAll('#foo li.bar'); for(var i=0; i<els.length; i++) { els[i].style.color = "green"; }
- abc
- def
- jkl
- mno
- pqr
- vwx
相対的な位置関係でのノードの取得
ノードはプロパティとして、以下のような他のノードへの参照を持っています。
プロパティ | 取得するノード |
---|---|
parentNode | 親ノード |
childNodes | 子ノードリスト(NodeList オブジェクト/ライブオブジェクト) |
firstChild | 最初の子ノード |
lastChild | 最後の子ノード |
previousSibling | 1つ前の兄弟ノード |
nextSibling | 次の兄弟ノード |
相対的な位置関係でノードを取得する場合に注意する必要があるのは、空白や改行もテキストノードとして扱われる点です。HTML を記述する際は、通常可読性を考えてタグごとに改行を入れますが、その場合改行の箇所にテキストノードが存在することになります。
以下はノードを参照するためのプロパティの使用例です。HTML を改行なしで記述した場合は、異なる結果になります。
<div id="foo"> <p id="first_p">first paragraph</p> <p id="second_p">second paragraph</p> <div id="bar"> <p id="third_p">third paragraph</p> <p id="fourth_p">fourth paragraph</p> </div> </div> <script type="text/javascript"> var bar = document.getElementById("bar"); var elem; elem = bar.parentNode; console.log(elem.id); //foo elem = bar.firstChild; console.log(elem.id); //undefined elem = bar.firstChild.nextSibling; console.log(elem.id); //third_p elem = bar.lastChild; console.log(elem.id); //undefined elem = bar.lastChild.previousSibling; console.log(elem.id); //fourth_p var children = bar.childNodes; console.log(children[0].id); //undefined console.log(children[1].id); //third_p console.log(children[2].id); //undefined console.log(children[3].id); //fourth_p console.log(children[4].id); //undefined </script>
改行や空白のテキストノードを除く必要がある場合は、取り出したノードが要素ノードかどうかを調べる必要があります。ノードの種類を判定するには、nodeType プロパティを調べます。
<ul id="foo"> <li>first</li> <li>second</li> <li>third</li> <li>fourth</li> </ul> <script type="text/javascript"> var result = []; var children = document.getElementById("foo").childNodes; for(var i = 0; i < children.length; i++) { var child = children[i]; //nodeType が要素かどうかを判定 if(child.nodeType == 1) { result.push(children[i].innerHTML); } } alert(result.join(",")); </script>
上記のコードの部分は、while ループと firstChild, nextSibling を使って以下のように書き換えることができます。
<script type="text/javascript"> var result = []; var child = document.getElementById("foo").firstChild; while(child) { //nodeType が要素かどうかを判定 if(child.nodeType == 1) { result.push(child.innerHTML); } child = child.nextSibling; } alert(result.join(",")); </script>
[追記]以下の情報は古くなっています。
Element Traversal API は削除される予定で、代わりに ChildNode と ParentNode のインターフェースに分割されています。但し、使い方に変わりはありません。
また、空白ノードやコメントノードを除外して要素だけを取得するための API(Element Traversal API) が規定されていて、最近のブラウザには実装されているのでそれを使用することもできます。children は Element Traversal API ではありませんが、主要なブラウザで実装されています。但し、IE の children は空白ノードを含んだ NodeList を返すので注意が必要です。
プロパティ | 取得するノード |
---|---|
children | 子要素リスト(ParentNode インターフェース) |
firstElementChild | 最初の子要素(ParentNode インターフェース) |
lastElementChild | 最後の子要素(ParentNode インターフェース) |
previousElementSibling | 1つ前の兄弟要素(NonDocumentTypeChildNode) |
nextElementSibling | 次の兄弟要素(NonDocumentTypeChildNode) |
childElementCount | 子要素の数(ParentNode インターフェース) |
<div id="foo"> <p id="first_p">first paragraph</p> <p id="second_p">second paragraph</p> <div id="bar"> <p id="third_p">third paragraph</p> <p id="fourth_p">fourth paragraph</p> </div> </div> <script type="text/javascript"> var bar = document.getElementById("bar"); var elem; elem = bar.parentNode; console.log(elem.id); //foo elem = bar.firstElementChild; console.log(elem.id); //third_p elem = elem.nextElementSibling; console.log(elem.id); //fourth_p elem = bar.lastElementChild; console.log(elem.id); //fourth_p elem = elem.previousElementSibling; console.log(elem.id); //third_p var children = bar.children; console.log(children[0].id); //third_p console.log(children[1].id); //fourth_p </script>
NodeList オブジェクト
NodeList オブジェクトは、配列のようなオブジェクトで DOM ノードの集合を表すオブジェクトです。但し、Array オブジェクトではないので NodeList を配列として扱ってしまうと、期待する動作が得られません。
getElementsByTagName()、getElementsByName()、getElementsByClassName() などのメソッドは NodeList オブジェクトを返します。NodeList オブジェクトで利用できるメンバには以下があります。
- length : リストに含まれる要素数を表すプロパティ
- item(n): 指定されたインデックス(n)に対応するリスト内の要素を返すメソッド(n は 0~length -1)。ただし、インデックスが範囲外の場合は null を返します。このメソッドは、シンプルなアクセス方法の nodeList[n] (この場合、n が範囲外の時には undefined が返ります)の代替として利用できます。
NodeList の各要素について処理を順次適用するには、以下のような方法があります。
for (var i = 0; i < myNodeList.length; ++i) { var item = myNodeList[i]; //または myNodeList.item(i) }
リストの要素についてループ処理のために for...in や for each...in は使えません。NodeList のプロパティである要素に加えて、length プロパティについても処理が適用されるため、element オブジェクトのみ処理すべきスクリプトではエラーが発生します。また、for..in で取得されるプロパティの順番は保証されていません。
NodeList の要素に対して1つずつ処理を行いたい場合は、同じ for 文でも、インデックス番号を使う記法にしなければいけません。
<ul id="foo"> <li>First</li> <li>Second</li> <li>Third</li> <li>Fourth</li> </ul>
以下は上記のような HTML がある場合、NodeList に対して for...in 文とインデックスを使った for 文の例です。for...in 文の場合、item メソッドや lenght プロパティについても処理がされています。
var nodeList = document.getElementById("foo").getElementsByTagName("li"); var result =""; for(var i in nodeList) { var node = nodeList.item(i); result += i + "番目: nodeType: " + node.nodeType + " nodeName: " + node.nodeName + "\n"; } alert("【for in 文の結果】 \n" + result); /* 【for in 文の結果】 0番目: nodeType: 1 nodeName: LI 1番目: nodeType: 1 nodeName: LI 2番目: nodeType: 1 nodeName: LI 3番目: nodeType: 1 nodeName: LI item番目: nodeType: 1 nodeName: LI namedItem番目: nodeType: 1 nodeName: LI length番目: nodeType: 1 nodeName: LI */
var nodeList = document.getElementById("foo").getElementsByTagName("li"); var result =""; for(var i = 0; i < nodeList.length; i++) { var node = nodeList.item(i); result += i + "番目: nodeType: " + node.nodeType + " nodeName: " + node.nodeName + "\n"; } alert("【for 文の結果】 \n" + result); /* 【for 文の結果】 0番目: nodeType: 1 nodeName: LI 1番目: nodeType: 1 nodeName: LI 2番目: nodeType: 1 nodeName: LI 3番目: nodeType: 1 nodeName: LI */
NodeList オブジェクトの中には、DOM に対する変更がリアルタイムで反映されるもの(ライブオブジェクト)もあります。getElementsByTagName() などの戻り値や Node.childNodes 等はこの性質を有しています。
以下の例では、id 属性が foo の(div)要素の子孫の p 要素を変数 elems に格納して個数を表示する例です。最初に取得した時点での elems.length は2ですが、p 要素を追加した後で表示すると、 elems.length が3になります。これがライブオブジェクトの特徴です。
<div id="foo"> <p>paragraph 1 </p> <p>paragraph 2 </p> </div> <script type="text/javascript"> var foo = document.getElementById("foo"); var elems = foo.getElementsByTagName("p"); alert(elems.length); //2 //p 要素を生成 var p = document.createElement("p"); //生成した p 要素にテキストを追加 p.appendChild(document.createTextNode("paragraph 3")); //foo に生成した p 要素を追加 foo.appendChild(p); alert(elems.length); //3 </script>
ライブオブジェクトを使用する際に注意する必要があるのは、以下のような for ループを行う場合です。以下では、div 要素の全てを取得して for ループでその div 要素に新しい div 要素を追加していますが、ループの継続条件の divs.length の値が毎回1つ増えるので、無限ループになってしまいます。
var divs = document.getElementsByTagName("div"); var newDiv; for (var i = 0; i < divs.length; i ++) { newDiv = document.createElement("div"); newDiv.appendChild(document.createTextNode("new div")); divs.item(i).appendChild(newDiv); }
このような場合は、予め divs.length を評価しておけば無限ループを避けることができます。
var divs = document.getElementsByTagName("div"); var newDiv; var len = divs.length; //alert(divs.length); (例)10 for (var i = 0; i < len; i ++) { newDiv = document.createElement("div"); newDiv.appendChild(document.createTextNode("new div")); divs.item(i).appendChild(newDiv); } //alert(divs.length); (例)20
NodeList を配列に変換
ループ処理で NodeList をそのまま使う場合と、一度配列に変換してから使う場合とで比較すると配列に変換してからの方がパフォーマンス的に優れています。
NodeList を配列に変換する方法の一つに、Array.prototype.slice を使う方法があります。
var array = Array.prototype.slice.call(document.getElementsByTagName('li'));
以下は Array.prototype.slice を使って配列に変換する例です。
var foo = document.getElementById('foo'); var nodeList_array = Array.prototype.slice.call(foo.getElementsByTagName('li')); var result = ""; for(var i = 0; i < nodeList_array.length; i ++) { result += nodeList_array[i].innerHTML + "\n"; } alert(result);
但し、IE8 以下ではエラーになるので確実なのは、ループで処理する方法です。
var foo = document.getElementById('foo'); var nodeList = foo.getElementsByTagName('li'); var nodeList_array = []; for (var i = 0, l = nodeList.length; i < l; ++i) { //配列に変換 nodeList_array.push(nodeList[i]); } var result = ""; for(var i = 0; i < nodeList_array.length; i ++) { result += nodeList_array[i].innerHTML + "\n"; } alert(result);
属性の変更・設定
多くの属性は、要素ノードの同名の(属性の)プロパティとしてアクセスすることができます。以下のようにしてぞれぞれのプロパティを取得・設定することができます。
<a id="foo" href="http://google.com" target="_blank">Link</a> <script type="text/javascript"> var a = document.getElementById("foo"); var href = a.href; var target = a.target; alert("href: " + href + " target: " + target); //href: http://google.com/ target: _blank a.href = "http://yahoo.com"; a.target = "_self"; alert("href: " + a.href + " target: " + a.target); //href: http://yahoo.com/ target: _self </script>
但し、属性名とプロパティ名が一致していない場合もあります。例えば、クラス属性は HTML では class ですが、DOM プロパティでは className になります。
<a id="foo" class="bar" href="http://google.com" target="_blank">Link</a> <script type="text/javascript"> var class_name = document.getElementById("foo").className; alert(class_name); //bar </script>
setAttribute()/getAttribute()
setAttribute()/getAttribute() を使うと、属性とプロパティの名前の違いを意識することなく属性の値を取得・変更することができます。
element.setAttribute(属性名, 属性値); //設定 element.getAttribute(属性名); //取得
<a id="foo" class="bar" href="http://google.com" target="_blank">Link</a> <script type="text/javascript"> var foo = document.getElementById("foo"); var class_name = foo.getAttribute("class"); alert(class_name); //bar foo.setAttribute("class", "foo"); alert(foo.getAttribute("class")); //foo </script>
不特定の属性値の取得
特定の要素ノードに属する全ての属性を取得するには、attributes プロパティを使用します。以下は、img タグに設定されている全ての属性値を取得する例です。
<img id="bg_img_top" src="images/header_bg/bg-img-top.jpg" alt="" title="Page title" width="1920" height="784" data-bgposition="center center" data-bgfit="cover" data-bgrepeat="no-repeat" data-bgparallax="5" class="rev-slidebg" data-no-retina> <script type="text/javascript"> var img = document.getElementById("bg_img_top"); var attrs = img.attributes; //全ての属性を取得 var result = []; for(var i = 0; i < attrs.length; i++) { var attr = attrs[i]; if(attr.nodeValue) { //nodeValue が空でない属性のみを取得 result.push(attr.nodeName + " : " + attr.nodeValue); } } alert(result.join("\n")); /*出力結果 id : bg_img_top src : images/header_bg/bg-img-top.jpg title : Page title data-bgposition : center center data-bgfit : cover data-bgrepeat : no-repeat data-bgparallax : 5 class : rev-slidebg height : 784 width : 1920*/ </script>
attributes プロパティは、要素ノードに設定されている全ての属性のリストを NamedNodeMap オブジェクトとして返します。NamedNodeMap オブジェクトに含まれるノード(属性)は for 文で取得できます。
取得した属性ノードの名前/値にアクセスするには、nodeName/nodeValue プロパティを使用します。
ノードの作成・追加
Document.createElement() メソッドと Document.createTextNode() メソッドは、それぞれ Element ノード と Text ノードを生成します。生成したノードは Node.appendChild() メソッドや Node.insertBefore() メソッドを使ってドキュメントに追加できます。また必要に応じて属性ノードを設定します。
以下は、id 属性が foo の p 要素にアンカータグを追加する例です。
<p id="foo"></p> <script type="text/javascript"> //要素ノードの生成 var a = document.createElement("a"); //属性を設定 a.href = "http://google.com"; a.target = "_blank"; //テキストノードを生成 var text = document.createTextNode("Google"); //要素ノードの直下にテキストノードを追加 a.appendChild(text); //追加先のノードを取得 var foo = document.getElementById("foo"); //追加先のノードに生成したノードを追加 foo.appendChild(a); </script>
ポイントは以下の3つです。
- 要素ノードとテキストノードの生成
Document.createElement() と Document.createTextNode() を使って要素ノードとテキストノードを生成
document.createElement(要素名); document.createTextNode(テキスト);
- 必要に応じて属性を設定
属性ノードを設定するには、属性と同名のプロパティを設定します。setAttribute() を使用して設定することもできます。または、createAttribute() メソッドで属性ノードを生成し、nodeValue プロパティで値を設定して、setAttributeNode メソッドで属性ノードを要素ノードに関連付けします。以下は、createAttribute() メソッドの使用例です。(前述の方法の方が簡潔)
//要素ノードの生成 var a = document.createElement("a"); //属性を設定(冗長) var href = document.createAttribute("href"); href.nodeValue = "http://google.com"; var target = document.createAttribute("target"); target.nodeValue = "_blank"; a.setAttributeNode(href); a.setAttributeNode(target);
属性は要素の子ノードではないので、removeChild() メソッドは使えず、属性を削除するには removeAttribute() メソッドを使用します。
- 生成したノードをドキュメントに追加
ノードは生成しただけでは、どこにも関連付けされていません。まず、テキストノードを appendChild() を使って要素ノードに追加します。そして、appendChild() または insertBefore() を使って要素ノードを文書に追加します。
Node.appendChild(追加するノード) Node.insertBefore(追加するノード, 対象ノード)
insertBefore() は、第1引数で指定したノードを、第2引数で指定したノードの直前に挿入します。第2引数に null を指定すると appendChild() 同様に、最後尾にノードを追加します。
ノードの置換・削除
ノードを置換するには、Node.replaceChild() メソッドを使用します。また、ノードを削除するには Node.removeChild() メソッドを使用します。
以下はノードを置換する例です。
<p id="foo"><a href="http://google.com" target="_blank">Google</a></p> <script type="text/javascript"> //要素ノードの生成 var newNode = document.createElement("a"); //属性を設定 newNode.href = "http://yahoo.com"; newNode.target = "_self"; //テキストノードを生成 var text = document.createTextNode("Yahoo"); //要素ノードの直下にテキストノードを追加 newNode.appendChild(text); //置換対象の親ノードを取得 var parent_node = document.getElementById("foo"); //置換対象のノード var oldNode = parent_node.firstChild; //または var oldNode = parent_node.getElementsByTagName("a")[0]; //置換 parent_node.replaceChild(newNode, oldNode); </script>
ノードを置換するには、replaceChild() メソッドを使用します。また、置換対象のノードは、現在のノードの子ノードでなければなりません(置換対象の親ノードで replaceChild() メソッドを呼び出す)。
Node.replaceChild(置換後のノード, 置換対象のノード)
ノードの削除
ノードを削除するには、removeChild() メソッドを使用します。削除対象のノードは現在のノードの子ノードでなければなりません(削除対象の親ノードで removeChild() メソッドを呼び出す)。
Node.removeChild(削除対象のノード)
以下はノードを削除する例です。
<p><a id="foo" href="http://google.com" target="_blank">Google</a></p> <script type="text/javascript"> var target_node = document.getElementById("foo"); target_node.parentNode.removeChild(target_node); </script>
innerHTML プロパティ
innerHTML は HTMLElement ノードのプロパティです。HTML 要素の innerHTML プロパティの値を読み出せば、その要素の子要素を表す HTML テキスト文字列が取得できます。 逆に、プロパティに値を設定すると、指定された文字列をパースし、解析結果をその要素の子要素に設定します。
以下は innerHTML プロパティの値を読み出す例です。
<ul id="foo"> <li>First</li> <li>Second</li> <li>Third</li> <li>Fourth</li> </ul> <script type="text/javascript"> var foo = document.getElementById("foo"); alert(foo.innerHTML); </script> /* 以下が出力 */ <li>First</li> <li>Second</li> <li>Third</li> <li>Fourth</li>
以下は innerHTML プロパティに値を設定する例です。
<div id="foo"></div> <script type="text/javascript"> document.getElementById("foo").innerHTML = '<p><a href="http://google.com">Link</a></p>'; </script> /* 以下は上記により設定された結果 */ <div id="foo"> <p><a href="http://google.com">Link</a></p> </div>
以下は、div 要素を生成し、その直下の HTML を innerHTML プロパティで設定し id="foo" の div 要素に挿入する例です。createElement() と appendChild() を何度も呼び出さずに要素を作成することができます。
<div id="foo"></div> <script type="text/javascript"> var div = document.createElement("div"); div.innerHTML = '<p class="bar"><a href="#">top</a></p>'; document.getElementById("foo").appendChild(div); </script>
とても便利ですが、外部からの入力など制御できない文字列を innerHTML を使って挿入するのはセキュリティリスクが発生するので注意が必要です。また、+= 演算子を使って innerHTML プロパティに少しずつテキストを追加していくのは、あまり効率的ではないようです。
textContent プロパティ
innerHTML プロパティは、HTML 文字列として取得・設定できますが、textContent プロパティは子要素まで含めてテキスト部分だけを取得・設定することができます。textContent プロパティを設定すると、子要素は全て削除され、テキストノードに置き換わります。
<div id="foo"><p>This is a <strong>sample</strong> text.</p></div> <script type="text/javascript"> var text = document.getElementById("foo").textContent; console.log(text); //This is a sample text. </script>
以下のように HTML 文字列を設定すると、それらはエスケープされ、タグは作成されません。
<div id="foo"></div> <script type="text/javascript"> document.getElementById("foo").textContent = "<p>This is a <strong>sample</strong> text.</p>"; </script>
HTML 上は以下のようにエスケープされます。テキストはHTMLとして解析されないため、パフォーマンスが向上する可能性があり、また、これは XSS 攻撃を防ぐことができます。
<div id="foo"><p>This is a <strong>sample</strong> text.</p></div>
CSS の操作
DOM を利用すればスタイルシート(CSS)を操作することができます。DOM でスタイルシートを操作するには以下の2つの方法があります。
- インラインスタイルを制御
(style プロパティを制御) - 外部スタイルシートの適用
(HTML の class 属性の値→className プロパティをスクリプトから制御)
インラインスタイルを制御
インラインスタイルとは、個々のノードに設定されたスタイルのことで HTML では以下のように style 属性に設定します。
<p style="background-color:blue;">青い背景</p>
DOM API を使えば、タグ名や ID からドキュメント要素の参照を取得できます。要素の取得ができれば、要素の style プロパティからそのドキュメント要素に 対する CSS2Properties オブジェクトが取得できます。この JavaScript オブジェクトには CSS1 と Css2 スタイル属性に対応するプロパティが存在し、これらのプロパティを設定すると、その要素の style 属性中で対応するスタイルを設定したのと同じ意味になります。
element.style.スタイルプロパティ名 = "値";
また、これらのプロパティから値を読み出せば、その要素の style 属性中で設定された CSS 属性値が返されます。但し、要素の style プロパティから取得した CSS2Properties オブジェクトには、その要素のインラインスタイルで指定したものしか反映されず、 その要素に適用されるスタイルシートについての情報は取得できません。 要素のすべての CSS プロパティの値を取得するには、代わりに window.getComputedStyle() を使用する必要があります。
以下は、id 属性が foo の p 要素のインラインスタイルを設定する例です。
<p id="foo">FOO</p> <script type="text/javascript"> document.getElementById("foo").style.color = "red"; </script>
以下は要素にマウスオーバーすると背景色(backgroundColor)を変化させる例です。マウスが外れると元の色に戻すようにしています。
<p onMouseOver="changeBGColor(this, 'green')" onMouseOut="changeBGColor(this, '#fefefe')">FOO</p> <script type="text/javascript"> function changeBGColor(elem, color) { elem.style.backgroundColor = color; } </script>
JavaScript での CSS 属性の命名規則
CSSスタイル属性では、多くの属性名中にハイフン(-)が含まれるが、JavaScriptではハイフンはマイナス記号と解釈されるため、 CSS2Propertiesオブジェクトのプロパティ名は実際のCSS属性名と若干異なる。
CSS 属性名にハイフンが含まれる場合は、ハイフンを取り除き、ハイフンの直後の文字を大文字にしたものが CSS2Properties プロパティの 名前になります。
例えば、border-left-width 属性は borderLeftWidth プロパティでアクセスできます。
また「float」という単語は JavaScript では予約語となっているため、float という名のプロパティを持てないので先頭に「css」をつけて cssFloat というプロパティ名にしています。要素の float 属性値を設定/取得するには CSS2Properties オブジェクトの cssFloat プロパティを使います。
スタイルプロパティの操作
CSS2Properties オブジェクトのスタイルプロパティを操作する場合、すべての値は文字列形式で指定します。
//スタイルシートや style 属性での記述 position: absolute; font-family: arial; background-color: #ffffff;
//ある要素 e に対して同じことをJavaScriptで行う場合(値をすべて引用符で囲む) e.style.position = "absolute"; e.style.fontFamily = "arial"; e.style.backgroundColor = "#ffffff";
また、位置指定関係のプロパティでは単位が必要となります。
e.style.left = 100; //誤り。文字列ではなく数値を指定 e.style.left = "100"; //誤り。単位がない e.style.left = "100px"; //正しい
何らかの計算を行って left プロパティに値を設定する場合など、計算結果に単位を必ず追加する必要があります。
e.style.left = (offsetX + left_margin + left_border + left_padding) + "px"; //単位を追加することで計算結果は文字列に変換されます。
これらのプロパティの値は数値ではなく文字列で表されるので次のようなコードはうまく動きません。(文字列+文字列になるので思ったような計算結果は得られない)
var totalMargin = e.style.marginLeft + e.style.marginRight; //NG
この場合、単純に加算をするには、parseInt() を使って以下のようにします。
var totalMargin = parseInt(e.style.marginLeft) + parseInt(e.style.marginRight);
ショートカット属性に対応するプロパティに値を設定する場合は次のようにします。
e.style.margin = topMargin + "px " + rightMargin + "px " + bottomMargin + "px " + leftMargin + "px";
以下のようにプロパティを個別に設定するより簡単です。
e.style.marginTop = topMargin + "px"; e.style.marginRight = rightMargin + "px"; e.style.marginBottom = bottomMargin + "px"; e.style.marginLeft = leftMargin + "px";
CSS2Properties のプロパティの値を読み出した場合、意味のある値が返されるのは、JavaScript コードで設定してある場合か、 HTML 要素でそのプロパティに対応する属性をインラインスタイル属性で指定してある場合だけです。例えば、スタイルシートで、ドキュメント中のすべての段落の左マージンを30ピクセルに設定してあるとすると、スタイルシートの設定を個々の style 属性で上書きしていない限り、 個々の段落要素の marginLeft プロパティには何も入っていません(空文字)。
算出スタイルの制御/getComputedStyle()
Webブラウザは、全てのスタイルシートのルールがその要素に適用されるかどうかをテストし、適用されるルールのスタイルと その要素のインラインスタイルを組み合わせて(算出して)、要素を表示します。算出スタイルは、要素に対して適用される全ての CSS プロパティにおいて最終的に算出された値です。
getComputedStyle()メソッドは、要素の算出スタイルを取得するための API で Window オブジェクトのメソッドです。
var style = window.getComputedStyle(element,pseudoElt);
- 第一引数(element):算出スタイルを調べる要素を指定
- 第二引数(pseudoElt):「:before」や「:after」などのCSS擬似要素を指定。通常要素には空文字列(または null)を指定。(省略可能)
getComputedStyle() メソッドの戻り値は CSS2Properties(CSSStyleDeclaration)オブジェクトで、このオブジェクトには、指定された要素や擬似要素に適用されるすべてのスタイルが格納されます。なお、このオブジェクトは読み出し専用です。
IE は getComputedStyle() メソッドをサポートしていませんが、その代わり、全ての HTML 要素は currentStyle プロパティを持ち、 このプロパティに算出スタイルが保持されます。(但し、擬似要素のスタイルを調べる方法はありません)
以下は id 属性が foo の要素の文字色の算出スタイルを取得する例です。
<script type="text/javascript"> var color = window.getComputedStyle(document.getElementById("foo"), null).color; alert(color); //rgb(51, 51, 51) </script>
ショートハンドの CSS プロパティは取得できないようです。
<script type="text/javascript"> var header = document.getElementById("header"); var style = window.getComputedStyle(header, ""); console.log(style.width); //1288px console.log(style.textAlign); //center console.log(style.border); //ショートハンドでは取得できない console.log(style.borderBottomWidth); //1px console.log(style.boxShadow); //rgba(255, 255, 255, 0.5) -1px 1px 1px 0px inset, rgba(255, 255, 255, 0.8) -1px 1px 0px 0px console.log(style.margin); //ショートハンドでは取得できない console.log(style.marginBottom); //3px console.log(style.backgroundColor); //rgb(251, 249, 249) console.log(style.position); //relative </script>
CSSStyleDeclaration.getPropertyValue() を利用すると、通常の CSS の書き方が使えます。
<script type="text/javascript"> var style = window.getComputedStyle(document.getElementById("header"), null); console.log(style.getPropertyValue('margin-bottom')); //3px </script>
スタイルの取得/getPropertyValue()
getPropertyValue() を使うと、指定した CSS のプロパティの値を取得することができます。
var value = style.getPropertyValue(property);
引数「property」には、CSS プロパティ名を指定します。
戻り値は、CSS プロパティの値(文字列)です。該当するものがない場合は、空の文字列が返されます。
var style = window.getComputedStyle(document.getElementById("side")); console.log(style.getPropertyValue('margin-top')); //例 10px
以下はインラインのスタイルを取得する例です。
<div id="foo" style="background-color:#EEEEEE;">foo</div>
var foo = document.getElementById("foo").style.getPropertyValue("background-color"); console.log(foo); //例 rgb(238, 238, 238)
CSS プロパティの値をセット/setProperty()
setProperty() は、CSS プロパティの値をセットするメソッドです。
style.setProperty(propertyName, value, priority);
- 引数
-
- propertyName:プロパティ名
- value:設定するプロパティの値
- priority:"important"を指定(オプション)
document.getElementById("foo").style.setProperty("background-color","blue");
但し、以下のように getComputedStyle()メソッドで取得したスタイルに対して、setProperty() で値を設定しようとすると、読み込み専用の値(オブジェクト)に設定しようとするため、エラーになります。
//以下はエラーになります window.getComputedStyle(document.getElementById("foo")).setProperty("background-color","blue"); //NoModificationAllowedError: Modifications are not allowed for this document
Chrome では、以下のようなエラーが表示されます。
Uncaught DOMException: Failed to execute 'setProperty' on 'CSSStyleDeclaration': These styles are computed, and therefore the 'background-color' property is read-only.
外部スタイルシートの適用
style プロパティを使って個々の CSS スタイルを制御する代わりに、HTML 要素の className プロパティを使って、HTML の class 属性の値を スクリプトから制御することもできます。クラスをスタイルシート中で適切に定義してあれば、要素のクラスを動的に変更することで、要素に適用されるスタイルを効果的に変更できます。
HTML の class 属性や、対応する className プロパティには2つ以上のクラスが設定できるので、className をスクリプトで制御する場合、 このプロパティにはクラス名が2つ以上保持されていることを考慮する必要があります。
element.className = "クラス名"; //複数のクラスを設定するには、半角スペース区切りでクラス名を指定 element.className = "クラス名1 クラス名2";
以下は2つのクラスを用意して、その要素にマウスオーバー及びマウスアウトするとクラスを変更して背景色を変更する例です。
<style> .foo { background-color: #F5CECF; } .bar { background-color: #BCDDF0; } </style> <div onMouseOver="changeBGC(this, 'foo')" onMouseOut="changeBGC(this, 'bar')">Sample Div</div> <script type="text/javascript"> function changeBGC(elem, clss) { elem.className = clss; } </script> //class という単語は予約語で使用できません。
注意する点としては、単にクラスを設定するだけだと、もしすでに他のクラスが指定されている場合、そのクラスが削除されてしまうことです。(クラスの追加ができない)
以下のような関数を作成することで、クラスの追加が可能になります。hasClassName() は、対象の要素(elem)に特定のクラス(clss)がすでに存在するかを調べる関数です。変数 classNameArray には、その要素に設定されているクラスをスペース区切りで分割して1つずつ配列の要素として格納します。複数のクラスの追加・削除ができるようにしています。replace(/^\s+|\s+$/g,'') は前後のスペースを削除するものです。
<script type="text/javascript"> function hasClassName(elem,clss) { var classNameArray = elem.className.split(' '); return classNameArray.indexOf(clss) >= 0; } function addClassName(elem,clss) { var classNameArray = elem.className.split(' '); var clss_Array = clss.split(' '); for(var i = 0; i < clss_Array.length; i++) { var index = classNameArray.indexOf(clss_Array[i]); if(index < 0){ classNameArray.push(clss_Array[i]); } } elem.className = classNameArray.join(' ').replace(/^\s+|\s+$/g,''); } //クラスの削除 function removeClassName(elem,clss) { var classNameArray = elem.className.split(' '); var clss_Array = clss.split(' '); for(var i = 0; i < clss_Array.length; i++) { var index = classNameArray.indexOf(clss_Array[i]); if(index >= 0){ classNameArray.splice(index, 1); elem.className = classNameArray.join(' '); console.log(i + " : " + elem.className); } } } //クラスの付け替え function toggleClassName(elem,clss) { var classNameArray = elem.className.split(' '); var clss_Array = clss.split(' '); for(var i = 0; i < clss_Array.length; i++) { hasClassName(elem,clss_Array[i]) ? removeClassName(elem,clss_Array[i]) : addClassName(elem,clss_Array[i]); } } addClassName(document.getElementById("foo"), "bar foo"); removeClassName(document.getElementById("foo"), "bar"); toggleClassName(document.getElementById("foo"), "foo bar"); </script>
スタイルシートの有効化と無効化
HTML の DOM レベル2標準では、<link> と <style> 要素に disabled プロパティが定義されています。 このプロパティに対応する disabled 属性は HTML タグにはありませんが、このプロパティの値を JavaScript から読み出したり設定したりできます。
disabled プロパティを true に設定した場合は、<link> や <style> 要素に関連するスタイルシートは無効となり、 ブラウザから無視されます。
以下の例では、<link> と <style> タグを使って4つのスタイルシートを定義して、それぞれに id 属性を指定します。
2つのスタイルシートは代替スタイルシート(標準では読み込まれない、 訪問者が選択可能なスタイルシートの指定方法)です。
代替スタイルシートは rel 要素を alternate stylesheet とし、title 属性でスタイル名を指定します。 title 属性を指定しないとスタイルシートが固定されてユーザーによる切り替えができないので注意が必要です。
<link rel="stylesheet" type="text/css" href="style.css" id="style0"> <link rel="alternate stylesheet" type="text/css" href="style1.css" id="style1" title="Large Type"> <link rel="alternate stylesheet" type="text/css" href="style2.css" id="style2" title="High Contrast"> <style id="style3" title="Sans Serif"> body {font-family: sans-serif; } </style>
以下の関数は、sheetid で指定されたスタイルシートを有効/無効にします。enabled にはフォームのチェックボックスの状態(this.checked)が渡されます。!演算子でその値をトグルしています。
<script type="text/javascript"> function enableSS(sheetid, enabled){ document.getElementById(sheetid).disabled = !enabled; } </script>
4つのチェックボックスを表示して、どのスタイルシートを有効にするかをユーザーが選択できるようにします。
<form> <input type="checkbox" onclick="enableSS('style0', this.checked)" checked/>Basics <input type="checkbox" onclick="enableSS('style1', this.checked)" />Large Type <input type="checkbox" onclick="enableSS('style2', this.checked)" />Contrast <input type="checkbox" onclick="enableSS('style3', this.checked)" checked/>Sans Serif </form>
イベント
JavaScript アプリケーションで、あるドキュメント要素のあるイベントについて処理を行いたい場合、その要素のイベントに対し イベントハンドラ(JavaScript関数)を登録します。その後、このイベントが発生すると、登録したイベントハンドラがブラウザから呼び出されます。
JavaScript のイベントとイベント処理は、以下に示すような互換性のない3つのイベント処理モデルが使われているため複雑になっています
- オリジナルイベントモデル:
このモデルは HTML4 標準である程度規定され、非公式にですが DOM レベル0 API の一部と見なされていて、機能は限定的ですが、すべてのブラウザでサポートされていて、可搬性があります。 - 標準イベントモデル:
DOM レベル2 で標準化されたモデル。IE 以外の最近のブラウザでサポートされています。 - Internet Explorerモデル:
IE4 で導入され、IE5 で拡張されたモデル。このモデルは、標準イベントモデルの高度な機能も一部サポートしているが全部ではありません。 つまり、高度なイベント処理機構を使いたい場合、IE 用に特別にコードを用意する必要があります。
イベントとイベントタイプ
オリジナルイベントモデルでは、イベントは Web ブラウザ内部で抽象化され、JavaScript コードから直接イベントを操作することはできません。 オリジナルイベントモデルでのイベントタイプは、実際にはイベントに応じて呼び出されるイベントハンドラの名前で区別します。このモデルでは、イベント処理を行うコードを HTML 要素で指定するか、関連する JavaScript オブジェクトの対応するプロパティを使って指定します。
例えば、あるハイパーリンクの上にユーザがマウスを動かしたことを知りたい場合は、ハイパーリンクを定義する<a>タグの onmouseover 属性を使います。また、ユーザが submit ボタンをクリックしたことを知りたい場合は、そのボタンと定義する <input> タグの onclick 属性か、 そのボタンを包含する <form> 要素の onsubmit 属性を使います。
レベル 2 イベントモデル(標準イベントモデル)で、ある特定の要素に対してイベントハンドラを登録するには、そのオブジェクトの addEventListener() メソッドを利用します。
イベントハンドラ | 発生契機(イベントハンドラが呼び出される条件) | サポートするHTML要素 |
---|---|---|
onabort | 画像のロードが中断された | <image> |
onblur | 要素が入力フォーカスを失った | <button>,<input>,<label>,<select>,<textarea>,<body> |
onchange | ユーザが<select>要素で項目を選択した。または、フォーム要素が入力フォーカスを失い、入力フォーカスが移されたときと要素の 値が変わっている。 | <input>,<select>,<textarea> |
onclick | マウスを一回クリックした。この後、mouseupイベントが発生する。デフォルトの動作(リンクをたどる、リセット、サブミットなど) を取り消す場合はfalseを返す。 | 大半の要素 |
ondblclick | ダブルクリックした。 | 大半の要素 |
onerror | 画像のロード中にエラーが発生した。 | <image> |
onfocus | 要素に入力フォーカスが移動された。 | <button>,<input>,<label>,<select>,<textarea>,<body> |
onkeydown | ユーザがキーを押した。取り消す場合はfalseを返す。 | フォーム要素と<body> |
onkeypress | ユーザがキーを押した。この後、keydownイベントが発生する。取り消す場合はfalseを返す。 | フォーム要素と<body> |
onkeyup | ユーザがキーから指を離した。 | フォーム要素と<body> |
onload | ロードが完了した。 | <body>,<framset>,<img> |
onmousedown | ユーザがマウスボタンを押した。 | 大半の要素 |
onmousemove | マウスが移動した。 | 大半の要素 |
onmouseout | マウスが要素から離れた。 | 大半の要素 |
onmouseover | マウスが要素の上に移動した。 | 大半の要素 |
onmouseup | ユーザがマウスボタンから指を離した。 | 大半の要素 |
onreset | フォームのリセットが要求された。リセットを無効にする場合はfalseを返す。 | <form> |
onresize | ウィンドウのサイズが変更された。 | <body>,<framset> |
onselect | テキストが選択された | <input>,<textarea> |
onsubmit | フォームの送信が要求された。送信を無効にするにはfalseを返す。 | <form> |
onunload | ドキュメントまたはフレームセットがアンロードされた。 | <body>,<framset> |
デバイス依存のイベントとデバイス独立のイベント
イベントハンドラ属性に注目すると、イベントは以下の2つに分類することができます。
- 入力イベント
- 「生のイベント」。ユーザがマウスを移動したりクリックしたり、キーボードのキーを押したときに発生します。この低レベルイベントは単に ユーザの動作を表しているだけです。
- セマンティックイベント
- ある特定のコンテキストでのみ発生する高レベルイベント。例えば、ブラウザがドキュメントのロードを完了したときや、フォームが送信されようとしているときなど。セマンティックイベントは、一般的に、低レベルイベントの副作用として発生します。例えば、ユーザが Submit ボタン上でマウスをクリックしたときには、onmousedown, onmouseup, onclick という3つのハンドラが呼び出さます。そして、マウスをクリックした結果、そのボタンを含む HTML フォームでは onsubmit イベントが発生します。
また、デバイスに依存するかどうかによっても、イベントを2つに分類することができます。
- デバイス依存イベント
- マウスやキーボードに関連付けされたイベント
- デバイス独立イベント
- 2つ以上の方法で発生するイベント。onsubmit, onchange などのセマンティックイベントは、一般的にデバイス独立イベント。
イベントハンドラ・リスナの設定
イベントに対する処理をイベントハンドラまたはイベントリスナと呼びます。イベントに対する処理の設定方法には以下のようなものがあります。
- HTML 要素の属性に指定(イベントハンドラ)※非推奨
- DOM 要素のプロパティに指定(イベントハンドラ)
- EventTarget.addEventListener() を利用(イベントリスナ)
イベントハンドラとイベントリスナはどちらも何らかの処理を実行させるためのイベントを設定するものですが、ここでは addEventListener() を使うものをイベントリスナと呼んでいます。
HTML 要素の属性に指定
イベントハンドラを設定する最も単純な方法は、HTML の属性として指定する方法です。この方法の利点は、ロードされた時点で確実にイベントハンドラが設定されていることです。
但し、この方法の場合、HTML と JavaScript が混在して読みにくくなるのと、イベントが増えると管理が大変になるので推奨されません。
<input type="button" value="Click Me" onclick="alert('thanks');">
イベントハンドラ属性の値は、JavaScript コードを表す文字列で、コードが複数の文で構成される場合、それぞれの文をセミコロン(;)で区切ります。
<input type="button" value="Click Here" onclick="if(window.numClicks) numClicks++; else numClicks = 1; this.value= 'Click # ' + numClicks;" />
文が複数になる場合は関数を別に定義し、HTMLのイベントハンドラからその関数を呼び出すほうが簡単です。
<input type="button" value="Click Here" onclick="show_numClicks(this)" /> <script type="text/javascript"> function show_numClicks(elem) { if(window.numClicks) { numClicks++; }else { numClicks = 1; } elem.value= 'Click # ' + numClicks; } </script>
DOM 要素のプロパティに指定
以下の方法は、ブラウザー間での互換性が高いです(IE8でも作動します)が、addEventListener() を使う方が複数のイベントハンドラを登録できたり、必要な時にイベントハンドラを削除できるなどのメリットがあります(IE9以降に対応)。
ドキュメント中の HTML 要素は、ドキュメントツリー中に対応する DOM 要素を持ちます。また、この JavaScript オブジェクトの プロパティが HTML 要素の属性に対応しています。これはイベントハンドラ属性にもあてはまり、<input>タグが onclick 属性を持てば、 この属性に記述されたイベントハンドラは、フォーム要素オブジェクトの onclick プロパティを使って参照できます。
JavaScript を使ってドキュメントの要素にイベントハンドラを設定するには、イベントハンドラプロパティに対して関数を設定します。
<input type="button" id="foo" value="Click Here" />
上記のような HTML の場合、このボタンは document.getElementById("foo"); で参照できるのでこのボタンにイベントハンドラを設定するには以下のようにします。HTML タグ内で属性として記述する場合と異なり、イベント名は必ず全て小文字で指定する必要があります。
var btn = document.getElementById("foo"); function sayHello() { alert("Hello"); } btn.onclick = sayHello; //イベントハンドラの設定 //btn.onclick = sayHello(); 誤り(NG) //関数を実行した戻り値を設定することになるので誤り
イベントハンドラに設定するのは、関数そのものなので、関数名の後に括弧がないことに注意。イベントハンドラを設定するには、関数を呼び出した結果ではなく、関数自身をイベントハンドラプロパティに 設定します。
以下のように記述することもできます。
var btn = document.getElementById("foo"); btn.onclick = function(){ alert('Hello');};
ページや画像を読み込み終わった時点にイベントハンドラを登録したい場合には、onload イベントを使います。
window.onload = function() { var btn = document.getElementById("foo"); function sayHello() { alert("Hello"); } btn.onclick = sayHello; }
DOM 要素のプロパティに設定すると、HTML タグの属性に記述したものは上書きされます。
また、この方法ではオブジェクトの1つのイベントに対して、1つのハンドラしか設定できません。同一のイベントに後から設定すると、先の設定が上書きされます。
以下の場合、ボタン要素をクリックしたときには、「Hello 2」と言うダイアログだけが表示されます。
var btn = document.getElementById("foo"); btn.onclick = function() { alert("Hello 1"); } btn.onclick = function() { alert("Hello 2"); }
イベントハンドラと this キーワード
HTML 属性で指定したのか、JavaScript プロパティで指定したのかに関わらず、イベントハンドラを定義すると、ドキュメント要素のプロパティに関数を保存することになります。つまり、ドキュメント要素に新しいメソッドを定義することになります。イベントハンドラが呼び出される場合、 イベントが発生した要素のメソッドとして呼び出されます。したがって、this キーワードが参照するのは、このイベントが発生した要素になります。
※ 但し、アロー関数を使用する場合はこの限りではありません。
addEventListener()
標準イベントモデル(DOM レベル2)で、ある特定の要素に対してイベントリスナ(イベントハンドラ)を登録するには、そのオブジェクトの addEventListener() メソッドを呼び出します。但し、このメソッドは IE 8 以前では使用できません(代わりに、attachEvent() メソッドが利用できます)。
target.addEventListener(type, listener [, useCapture]);
このメソッドには引数を3つ渡します。
- type
- イベントリスナを登録するイベントタイプ(イベント名)。イベントタイプは HTML のハンドラ属性の名前から「on」を取り除いた文字列で指定します。 全て小文字で記述します。
- listener
- 指定した種類のイベントが発生したときに呼び出されるリスナ関数を指定。リスナ関数が呼び出されるときには、引数はただ1つで、Event オブジェクトが渡され、このオブジェクトにはイベントに関する詳細な情報(どのマウスボタンが押されたのか等)が格納されます。
- useCapture
- 論理値。true の場合、イベント伝播のキャプチャリング段階で、指定されたイベントリスナがイベントをキャプチャします。 false の場合、イベントリスナは通常のイベントリスナとなり、イベントが直接そのオブジェクトで発生した場合と、要素の子孫要素で発生し、この要素までバブリングしてきた場合に、このイベントリスナが呼び出されます。
- true : キャプチャリングフェーズ (捕捉フェーズ)で処理
- false : ターゲットフェーズとバブリングフェーズで処理
var btn = document.getElementById("foo"); function sayHello() { alert("Hello"); } btn.addEventListener('click', sayHello, false);
複数のリスナ(ハンドラ)の登録
DOM レベル2 では、同一オブジェクトの同一イベントに対して、複数のイベントリスナを登録することができます。
以下の場合、ボタン要素をクリックしたときには、「Hello 1」と「Hello 2」と言う2つのダイアログが表示されます。
var btn = document.getElementById("foo"); btn.addEventListener('click', function() { alert("Hello 1"); }, false); btn.addEventListener('click', function() { alert("Hello 2"); }, false);
但し、同一イベントに同一のイベントリスナを重複して設定すると、後の設定は無視されます。以下の場合、ボタン要素をクリックすると、ダイアログが1度だけ表示されます。
var btn = document.getElementById("foo"); function sayHello() { alert("Hello"); } btn.addEventListener('click', sayHello, false); btn.addEventListener('click', sayHello, false);
addEventListener() と this キーワード
レベル0 イベントモデルでは、関数がドキュメント要素のイベントハンドラとして登録されると、そのドキュメント要素のメソッドになります。イベントハンドラが呼び出されると、その要素のメソッドとして呼び出され、関数中では、this キーワードはイベントが発生した要素を参照します。
DOM レベル2 ではイベントリスナ(ハンドラ)関数がどのように呼び出されるかや、this キーワードの値については何も規定されていません。現在の実装では addEventListener() で登録されたリスナはターゲット要素のメソッドとして呼び出され、リスナが呼び出されると、this キーワードはリスナが登録されたオブジェクトを参照します。
仕様で規定されていない動作を使いたくない場合は、リスナ関数に渡される Event オブジェクトの currentTarget プロパティを使います。currentTarget プロパティはイベントリスナが 登録されたオブジェクトを参照します。
以下はクラス属性が foo の要素をクリックするとその文字色を赤に変える例です。
<p class="foo">foo1</p> <p class="foo">foo2</p> <p class="foo">foo3</p> <script> var foo = document.getElementsByClassName("foo"); for(var i = 0; i < foo.length; ++i){ // 取得した HTML 要素の数だけループ foo[i].addEventListener("click", function(){ // クリックイベントを登録 this.style.color = "red"; // この this はクリックされた要素自身 }, false); } </script>
または(繰り返し回数が多い場合はこちらの方がメモリの消費が抑えられるので効率的です)
<p class="foo">foo1</p> <p class="foo">foo2</p> <p class="foo">foo3</p> <script> var foo = document.getElementsByClassName("foo"); function changeColor() { this.style.color = "red"; } for(var i = 0; i < foo.length; ++i){ // 取得した HTML 要素の数だけループ foo[i].addEventListener("click", changeColor, false); } </script>
this の参照先を他のローカル関数などでも使用したい場合は、メソッド内で別の変数を定義し、その中にthis の値を代入すれば、内部関数ではその変数を通じて this の値にアクセスできるようになります(変数の名前は that や self とするのが通例)。ローカル関数の中の this は windows オブジェクトになりますが、定義した変数を使用することで操作することができます。
以下はローカル関数 changeBlue を定義して、id 属性が bar の要素は文字色を青にする例です。
<p class="foo">foo1</p> <p class="foo" id="bar">foo2</p> <p class="foo">foo3</p> <script> var foo = document.getElementsByClassName("foo"); for(var i = 0; i < foo.length; ++i){ foo[i].addEventListener("click", function(){ var that = this; var changeBlue = function() { if(that) that.style.color = "blue"; } if(that.id === "bar") {//this.id でもここはOK changeBlue(); }else{ this.style.color = "red"; } }, false); } </script>
イベントリスナの削除
ハンドラを削除するには、removeEventListener() を使います。
element.removeEventListener(type,listener,useCapture)
- type
- 登録されたイベントを表す文字列。(リスナ登録時に addEventListener() で指定したイベント)
- listener
- 登録時のハンドラ関数
- useCapture
- 論理値。削除される EventListener がキャプチャリスナーとして登録されていたか否かを指定します。 指定されない場合、デフォルトの false が適用されます。
var btn = document.getElementById("foo"); function sayHello() { alert("Hello"); } btn.addEventListener('click', sayHello, false); btn.removeEventListener('click', sayHello, false);
但し、登録時にリスナとして無名関数を使った場合、この方法では削除することができません。方法がないわけではありませんが、後から削除する必要がある場合は、リスナに名前を付けたほうが簡単です。
var btn = document.getElementById("foo"); btn.addEventListener('click', function() { alert("Hello 1"); }, false); //無名関数を使った場合は、以下の方法では削除できない btn.removeEventListener('click', function() { alert("Hello 1"); }, false);
イベントリスナオブジェクト
イベントリスナ(ハンドラ)としては、関数を指定するのが一般的ですが、ブラウザによっては、handleEvent() メソッドを実装したオブジェクトを指定することができます。
var btn = document.getElementById("foo"); var eventListener = { //handleEvent() メソッドを実装したオブジェクト text : "Hello from object", handleEvent: function(e) { alert(this.text + " /Event: " + e); } } btn.addEventListener('click', eventListener, false); //リスナの削除 btn.removeEventListener('click', eventListener, false);
上記の場合、ボタン要素をクリックすると Hello from object /Event: [object MouseEvent] とダイアログが表示されます。
attachEvent() IEでのイベントリスナの登録・削除
IE9より前は addEventListener() をサポートしていませんが attachEvent() とdetachEvent() メソッドが用意されています。このメソッドにより、あるオブジェクトの、あるイベントタイプに対して、複数のリスナ関数を登録することができます。但し、IE11 以降ではこのメソッドはサポートされていません(使用できません)。
attachEvent() を使って 登録されたイベントリスナには、イベントリスナ呼び出し時に、グローバルな window.event オブジェクトのコピーが引数として渡されます。 attachEvent() の書式は以下になります。
attached = target.attachEvent(eventNameWithOn, callback)
- target
- 発行されたイベントを受け取る DOM 要素
- eventNameWithOn
- イベントハンドラの属性のような、接頭辞に"on"が付加されたイベントを受け取るためのイベント名。例えば、クリックイベントを受け取るためには "onclick" を指定。
- callback
- このイベントが発行されたときに呼ばれるコールバック関数。この関数は引数無しで呼ばれ、this の参照先は window オブジェクトです。
- attached
- イベントリスナの付加に成功したかどうかを判断するBoolean型の返り値
var btn = document.getElementById("foo"); function hello_ie() { alert("Hello IE"); } btn.attachEvent('onclick', hello_ie);
イベントリスナを削除するには、detachEvent() を使います。
target.detachEvent(eventNameWithOn, callback)
- eventNameWithOn
- リスナ登録時に指定したイベント名(接頭辞に"on"が付加されたイベント名)。
- callback
- リスナ登録時に指定したコールバック関数。
クロスブラウザに対応
クロスブラウザに対応するには、以下のような関数を用意します。以下の addListener 関数では、addEventListener/attachEvent が利用できるかを確認(機能テスト)し、対応しているメソッドを使ってハンドラ(リスナ)を登録します。
function addListener(elem, ev, listener) { if(elem.addEventListener) { elem.addEventListener(ev, listener, false); }else if(elem.attachEvent) { elem.attachEvent('on' + ev, listener); }else { throw new Error("イベントリスナ未対応"); } } var btn = document.getElementById("foo"); function sayHello() { alert("Hello"); } addListener(btn, "click", sayHello);
リスナを削除するには、以下のような関数を用意します。
function removeListener(elem, ev, listener) { if(elem.removeEventListener) { elem.removeEventListener(ev, listener, false); }else if(elem.detachEvent) { elem.detachEvent('on' + ev, listener); }else { throw new Error("イベントリスナ未対応"); } } removeListener(btn, "click", sayHello);
Event オブジェクト
イベントモジュールとイベントタイプ
DOM レベル2 はいくつかのモジュールに分割されていています。次のようなコードで、ブラウザが EventAPI をサポートしているかをテストすることができます。
document.implementation.hasFeature("Events", "2.0")
Events モジュールには、基本的なイベント処理用の API しか含まれていません。ある特定のイベントタイプはサブモジュールでサポートします。
例えば、MouseEvents サブモジュールがサポートしているのは、mousedown, mouseup, click などのイベントタイプです。 MouseEvent インターフェースも定義し、MouseEvents モジュールでサポートされるイベントタイプ用のハンドラ関数には、MouseEvent インターフェースを実装するオブジェクトが引数として渡されます。
以下は、各イベントモジュール、モジュールが定義するイベントインターフェース、モジュールがサポートするイベントタイプを表にしたものです。
モジュール名 | イベントインターフェース | イベントタイプ |
---|---|---|
HTMLEvents | Event | abort, blur, change, error, focue, load, reset, resize, scroll, select, submit, unload |
MouseEvents | MouseEvent | click, mousedown, mousemove, mouseout, mouseover, mouseup |
UIEvents | UIEvent | DOMActivate, dOMFocusIn, DOMFocusOut |
イベントが発生したとき、そのイベントのハンドラには引数としてオブジェクトが渡されます。このオブジェクトは、イベントタイプに関連した Event インターフェースを実装します。このオブジェクトのプロパティにより、イベントについての詳細な情報が提供されます。イベントハンドラではこの情報を活用して処理を行います。
次の表は標準イベントのリストを示し、イベントタイプごとにまとめなおしたものです。
- 2列目:ハンドラに渡されるEventオブジェクトの種類(インターフェース名)
- 3列目(B):イベント伝播でドキュメント階層をバブリングする(上がっていく)かどうか
- 4列目(D):preventDefault()で中止できるデフォルト動作を持つかどうか
- 5列目:HTMLEventsモジュールのイベントについては、どのHTML要素がこのイベントを生成できるのか。他のイベントタイプについては、Eventオブジェクトのどのプロパティがイベントの詳細な情報を保持しているかを示している。 但し、このプロパティにはEventインターフェースのプロパティは含まれていない。
イベントタイプ | インターフェース | B | D | サポート要素/詳細プロパティ |
---|---|---|---|---|
abort | Event | ○ | × | <img>,<object> |
blur | Event | × | × | <a>, <area>, <button>, <input>, <label>,<select>,<textarea> |
change | Event | ○ | × | <input>,<select>,<textarea> |
click | MouseEvent | ○ | ○ | screenX, screenY, clientX, clientY, altKey, ctrlKey, shiftKey, metaKey, button, detail |
error | Event | ○ | × | <body>,<frameset>,<img>,<object>, |
focus | Event | × | × | <a>, <area>, <button>, <input>, <label>,<select>,<textarea> |
load | Event | × | × | <body>,<frameset>,<iframe>,<img>,<object>, |
mousedown | MouseEvent | ○ | ○ | screenX, screenY, clientX, clientY, altKey, ctrlKey, shiftKey, metaKey, button, detail |
mousemove | MouseEvent | ○ | × | screenX, screenY, clientX, clientY, altKey, ctrlKey, shiftKey, metaKey |
mouseout | MouseEvent | ○ | ○ | screenX, screenY, clientX, clientY, altKey, ctrlKey, shiftKey, metaKey, relatedTarget |
mouseover | MouseEvent | ○ | ○ | screenX, screenY, clientX, clientY, altKey, ctrlKey, shiftKey, metaKey, relatedTarget |
mouseup | MouseEvent | ○ | ○ | screenX, screenY, clientX, clientY, altKey, ctrlKey, shiftKey, metaKey, button, detail |
reset | Event | ○ | × | <form> |
resize | Event | ○ | × | <body>,<frameset>,<iframe> |
scroll | Event | ○ | × | <body> |
select | Event | ○ | × | <input>,<textarea> |
submit | Event | ○ | ○ | <form> |
unload | Event | × | × | <body>,<frameset> |
DOMActive | UIEvent | ○ | ○ | detail |
DOMFocusIn | UIEvent | ○ | × | なし |
DOMFocusOut | UIEvent | ○ | × | なし |
イベントインターフェースとイベントの詳細
イベントが発生すると、DOMレベル2API では、イベントについての詳細情報(いつどこで発生したかなど)をオブジェクトのプロパティに格納し、イベントハンドラに引数として渡します。イベントモジュールごとに、そのイベントタイプにとって適切な情報を表すインターフェースを持ちます。 イベントモジュールは3つあり、イベントインターフェースも3つあります(これら3つのインターフェースはお互いに関係があり、階層構造を持ちます)。
- Event インターフェース
- この階層構造のルート。この基本イベントインターフェースは、すべての Event オブジェクトで実装される。
- UIEvent インターフェース
- Event インターフェースのサブインターフェース。UIEvent インターフェースを実装する Event オブジェクトは Event インターフェースのメソッドや プロパティもすべて実装する。
- MouseEvent インターフェース
- UIEvent インターフェースのサブインターフェース。MouseEvent インターフェースを実装する Event オブジェクトは、MouseEvent、 UIEvent、Event インターフェースで定義されるメソッドやプロパティを全て実装している。
Event インターフェース
イベントハンドラ(リスナ)には、発生したイベント自体が引数として渡されます。イベントハンドラでは、発生したイベントを調べることによりイベントの種類やイベントの発生したノードにより処理を分岐することができます。
全ての Event オブジェクトは Event インターフェースを実装しています。以下は Event インターフェースのプロパティです。
プロパティ | 概要 |
---|---|
type | 発生したイベントタイプ。このプロパティの値はイベントタイプの名前。イベントハンドラを登録するときに使う文字列と同じ。 (「click」や「mouseover」など) |
target | イベントが発生したノード。必ずしも currentTarget と同じ値とは限りません。 |
currentTarget | イベントを現在処理しているノード。(イベントハンドラを現在実行中のノード)。イベント伝播のキャプチャリング段階やバブリング段階でイベントが 処理される場合、このプロパティの値は target プロパティの値とは異なります。イベントハンドラ関数中で this キーワードの変わりにこのプロパティが使えます。 |
eventPhase | イベント伝播のどの段階を現在処理中かを表す数値(定数)。値は、Event.CAPTURING_PHASE, Event.AT_TARGET, Event.BUBBLING_PHASE のうちいずれか。 |
timeStamp | イベントが発生した時間を表すDateオブジェクト。 |
bubbles | このイベント(タイプ)がドキュメントのツリー構造をバブリングするかどうかを表す論理値。 |
cancelable | イベントがデフォルトの動作を持ち、そのデフォルトの動作が preventDefault() メソッドで中止可能かどうかを表す論理値。 |
これらの7つのプロパティ以外にEventインタフェースでは2つのメソッドが定義されています。これらのメソッドも全ての Event オブジェクトで実装されています。
メソッド | 概要 |
---|---|
stopPropagation() | イベントハンドラから stopPropagation() を呼び出せば、現在処理しているノードより先にイベントが伝播しなくなります。 |
preventDefault() | preventDefault() を呼び出すのはレベル0イベントモデルで false を返すのと同じような働きをします。 |
UIEvent インターフェース
UIEvent インタフェースは Event インタフェースのサブインタフェースで、DOMFocusIn, DOMFocusOut, DOMActivate イベントタイプ発生時に渡される Event オブジェクトの型を定義します。これらのイベントタイプはあまり使われません。
プロパティ | 概要 |
---|---|
view | イベントが発生した Window オブジェクト(DOM の用語では view と呼ぶ) |
detail | イベントについての補助情報を提供する数値。 click, mousedown, mouseup イベントの場合は、このフィールドにはクリック数が格納されます。DOMActivate イベントでは、このフィールドは、単純な活性化の場合には1になり、ダブルクリックや Shift + Enter キーのような高度な活性化の場合は2になります。 |
MouseEvent インターフェース
MouseEvent インタフェースは Event と UIEvent のプロパティやメソッドを継承し、さらに次のようなプロパティがあります。
プロパティ | 概要 |
---|---|
button | mousedown, mouseup, click イベント時にどのボタンの状態が変化したかを表す数値。0は左ボタン、1は中ボタン、2は右ボタンを表す。 このプロパティはボタンの状態が変化したときだけ使われます。 |
altKey, ctrlKey, metaKey, shiftKey | これらの値はマウスイベント発生時に、それぞれ、Altキー、Ctrlキー、Metaキー、Shiftキーが押されていたかどうかを表す論理値。 button プロパティと異なり、これらのプロパティはすべてのマウスイベントで有効。 |
clientX, clientY | マウスポインタの X,Y 座標(クライアント領域またはブラウザウィンドウからの相対座標)を表します。 但し、これらの座標には、ドキュメントのスクロールは計算に入っていません。 |
screenX, screenY | マウスポインタのX,Y座標(画面座標)を表すします。これらの値は、マウスイベントが発生した場所の近くに新しいブラウザウィンドウなどを開く場合などに利用できます。 |
relatedTarget | このプロパティは、イベントのターゲットノードに関連するノードを参照します。mouseover イベントの場合は、ターゲットにマウスが移動したときに、マウスが離れたノードを参照します。mouseout イベントの場合は、ターゲットを離れたときに、マウスの移動先のノードを参照します。 |
イベントに関わる情報の取得
以下は、mousedown イベントのプロパティを全て列挙する例です。
function addListener(elem, ev, listener) { //リスナの登録 if(elem.addEventListener) { elem.addEventListener(ev, listener, false); }else if(elem.attachEvent) { elem.attachEvent('on' + ev, listener); }else { throw new Error("イベントリスナ未対応"); } } var btn = document.getElementById("foo"); function showEventsProp(e) { //for in でイベント(e)のプロパティを列挙 var result = []; for(var prop in e){ result.push(prop + " 値: " + e[prop]); } console.log(result.join('\n')); } addListener(btn, "mousedown", showEventsProp);
前述の例では、全てのプロパティを列挙しましたが、特定のプロパティとその値を取得するには、以下のようにします。以下はマウスダウン時に、イベント発生の要素、マウスポインタの位置、ボタンの種類を表示する例です。
function addListener(elem, ev, listener) { if(elem.addEventListener) { elem.addEventListener(ev, listener, false); }else if(elem.attachEvent) { elem.attachEvent('on' + ev, listener); }else { throw new Error("イベントリスナ未対応"); } } var btn = document.getElementById("foo"); function showEventsProp(e) { var result = []; result.push("発生元要素:" + e.target.id); result.push("イベントタイプ: " + e.type); result.push("ボタン:" + getButtonType(e)); result.push("X座標:" + e.clientX); result.push("Y座標:" + e.clientY); console.log(result.join('\n')); } function getButtonType(e) { switch(e.button) { case 0: return "left"; case 1: return "center"; case 2: return "right"; } } addListener(btn, "mousedown", showEventsProp); /*表示例 発生元要素:foo イベントタイプ: mousedown ボタン:left X座標:222 Y座標:114*/
IE9 以下を対象にする場合は、少し変更が必要です。IE 以外のブラウザでは、イベント発生元の要素を取得するには「target」を使いますが、古い IE では「srcElement」プロパティを使用する必要があります。また、古い IE では Event オブジェクトをイベントハンドラの引数としては渡さないので、winodw.event プロパティを経由して明示的に Event オブジェクトを取得する必要があります。
以下ではクロスブラウザに対応するため、イベント発生元の要素を取得するための、getSource 関数を定義しています。また古い IE では、マウスボタンの種類を表す button プロパティの値が異なっているので、getButtonType 関数を用意しています。
function addListener(elem, ev, listener) { if(elem.addEventListener) { elem.addEventListener(ev, listener, false); }else if(elem.attachEvent) { elem.attachEvent('on' + ev, listener); }else { throw new Error("イベントリスナ未対応"); } } var btn = document.getElementById("foo"); function showEventsProp(e) { var result = []; result.push("発生元要素:" + getSource(e).id); result.push("イベントタイプ: " + e.type); result.push("ボタン:" + getButtonType(e)); result.push("X座標:" + e.clientX); result.push("Y座標:" + e.clientY); console.log(result.join('\n')); } function getSource(e) { if(e.target) { //Event.target プロパティが存在するか return e.target; }else if (window.event) { return window.event.srcElement; } } function getButtonType(e) { if(e.target) { switch(e.button) { case 0: return "left"; case 1: return "center"; case 2: return "right"; } }else if (window.event) { //IE用 switch(window.event.button) { case 1: return "left"; case 4: return "center"; case 2: return "right"; } } } addListener(btn, "mousedown", showEventsProp);
イベントの伝播
HTML ドキュメントは、HTML 要素が入れ子になって構成されています。デフォルトでは、ページ上で発生したイベントは、上位の要素にも通知されます。
以下のような入れ子になった div 要素で、イベントが上位要素に通知されることを確認してみます。
<div id="foo"> <div id="bar">Click Here</div> </div>
以下のように両方の要素にイベントリスナを登録します。
function addListener(elem, ev, listener) { if(elem.addEventListener) { elem.addEventListener(ev, listener, false); }else if(elem.attachEvent) { elem.attachEvent('on' + ev, listener); }else { throw new Error("イベントリスナ未対応"); } } var foo = document.getElementById("foo"); var bar = document.getElementById("bar"); function fooAlert(e) { alert("foo: 上位要素のリスナ"); } function barAlert(e) { alert("bar: 下位要素のリスナ"); } addListener(foo, 'click', fooAlert); addListener(bar, 'click', barAlert);
この時、下位の要素(id = "bar の div 要素)をクリックすると、ダイアログが2回表示され、イベントが上位要素にも通知されることが確認できます。このようにイベントが上位要素に順番に伝播することをバブリング(bubbling)と言います。
レベル0 イベントモデルでは、イベントが発生した要素に対して、ブラウザがそのイベントをディスパッチし、そのオブジェクトに対して適切なイベントハンドラが存在すれば、このハンドラが実行され、それ以上の処理は何もありません。
DOM レベル2 のイベントモデルでは、あるドキュメント要素でイベントが発生したときに(イベントが発生した要素をイベントターゲットと言います)、イベントターゲットの ハンドラが呼び出され、さらにこのイベントターゲットの先祖要素のハンドラにも、このイベントを処理する機会が与えられます。イベント伝播の処理は以下の3つの段階で構成されます。
- キャプチャリングフェーズ Document オブジェクトから DOM ツリーを下って、ターゲットノードにイベントが伝播していくフェーズです。 ターゲットの先祖要素のいずれかがキャプチャリング用のイベントハンドラを登録している場合は、イベント伝播のこの段階で実行されます。
- ターゲットフェーズ 次の段階はターゲットノード自身で行われ、このターゲットノードに登録されたイベントハンドラが実行されます。HTML タグの属性としてイベントハンドラが設定されている場合や、オブジェクトのプロパティとしてイベントハンドラが設定されている場合はここで実行されます。
- バブリングフェーズ イベントが、ターゲット要素から Document オブジェクトまで DOM ツリーを上に上がっていくフェーズです。 イベント伝播のキャプチャリング段階の処理はすべてのイベントで行われますが、バブリング段階の処理は全てのイベントに対して行われるわけではありません。 一般的に、生の入力イベントはバブリングし、 高レベルのセマンティックイベントは バブリングしません。どのイベントがバブリングし、どのイベントがバブリングしないかは「別表」を参照ください。
伝播のキャンセル
イベントを伝播させないようにするには、Event.stopPropagation() メソッドをイベントリスナ(ハンドラ)内で実行します。Event.stopPropagation() メソッドは、バブリングを抑止し次意向に伝播されるリスナーターゲットに設定されているイベントリスナが実行されなくします(現在のリスナーターゲットに設定されている他のイベントリスナは実行されます)。
function addListener(elem, ev, listener) { if(elem.addEventListener) { elem.addEventListener(ev, listener, false); }else if(elem.attachEvent) { elem.attachEvent('on' + ev, listener); }else { throw new Error("イベントリスナ未対応"); } } var foo = document.getElementById("foo"); var bar = document.getElementById("bar"); function fooAlert(e) { alert("foo: 上位要素のリスナ"); } function barAlert(e) { alert("bar: 下位要素のリスナ"); e.stopPropagation(); //バブリングを抑止 } addListener(foo, 'click', fooAlert); addListener(bar, 'click', barAlert);
バブリングを抑制する方法も、古い IE では異なっていて、cancelBubble プロパティを利用します。必要であれば、以下のような関数(cancelBubbling)を作成して対応することができます。
function cancelBubbling(e) { if(e.stopPropagation) { e.stopPropagation(); }else if (window.event) { window.event.cancelBubble = true; } } function addListener(elem, ev, listener) { if(elem.addEventListener) { elem.addEventListener(ev, listener, false); }else if(elem.attachEvent) { elem.attachEvent('on' + ev, listener); }else { throw new Error("イベントリスナ未対応"); } } var foo = document.getElementById("foo"); var bar = document.getElementById("bar"); function fooAlert(e) { alert("foo: 上位要素のリスナ"); } function barAlert(e) { alert("bar: 下位要素のリスナ"); cancelBubbling(e); //作成した関数を利用 } addListener(foo, 'click', fooAlert); addListener(bar, 'click', barAlert);
標準処理のキャンセル
Web ブラウザが標準で実装している処理(デフォルトの動作)を実行させないこともできます。それには Event.preventDefault() メソッドを使用します。
例えば、a 要素をクリックするとそのリンク先にページが移動しますが、preventDefault() メソッドを実行すると、このデフォルトの動作が実行されません。Event.preventDefault() メソッドは、HTML 属性や DOM のプロパティで指定するイベントハンドラで false を返すのと同じような働きをします。
但し、イベントの中には、preventDefault() メソッドで中止できないイベントもあります。どのイベントで中止できないかは「別表」を参照ください。
<a id="foo" href="http://google.com">Link</a> <script type="text/javascript"> function addListener(elem, ev, listener) { if(elem.addEventListener) { elem.addEventListener(ev, listener, false); }else if(elem.attachEvent) { elem.attachEvent('on' + ev, listener); }else { throw new Error("イベントリスナ未対応"); } } var foo = document.getElementById("foo"); function fooAlert(e) { alert("foo"); e.preventDefault(); } addListener(foo, 'click', fooAlert); </script>
標準処理のキャンセルも、古い IE では異なっていて、returnValue プロパティを利用します。必要であれば、以下のような関数(cancelEvent)を作成して対応することができます。
function cancelEvent(e) { if(e.preventDefault) { e.preventDefault(); }else if(window.event) { window.event.returnValue = false; } } function addListener(elem, ev, listener) { if(elem.addEventListener) { elem.addEventListener(ev, listener, false); }else if(elem.attachEvent) { elem.attachEvent('on' + ev, listener); }else { throw new Error("イベントリスナ未対応"); } } var foo = document.getElementById("foo"); function fooAlert(e) { alert("foo"); cancelEvent(e); //作成した関数を利用 } addListener(foo, 'click', fooAlert);