イベントとは、何かが発生したという信号です。すべてのDOMノードはこうした信号を生成します(ただし、イベントはDOMに限定されません)。
最も有用なDOMイベントのリストを以下に示します。
マウスイベント
click
– マウスが要素をクリックしたとき(タッチスクリーンデバイスではタップで発生します)。contextmenu
– マウスで要素を右クリックしたとき。mouseover
/mouseout
– マウスカーソルが要素の上に来たとき/要素から離れたとき。mousedown
/mouseup
– マウスボタンが要素の上で押されたとき/離されたとき。mousemove
– マウスが移動したとき。
キーボードイベント
keydown
およびkeyup
– キーボードのキーが押されたときと離されたとき。
フォーム要素イベント
submit
– 訪問者が<form>
を送信したとき。focus
– 訪問者が要素(例:<input>
)にフォーカスしたとき。
ドキュメントイベント
DOMContentLoaded
– HTMLが読み込まれて処理され、DOMが完全に構築されたとき。
CSSイベント
transitionend
– CSSアニメーションが終了したとき。
他にも多くのイベントがあります。今後の章で、特定のイベントの詳細について説明します。
イベントハンドラ
イベントに反応するために、ハンドラ(イベントが発生した場合に実行される関数)を割り当てることができます。
ハンドラは、ユーザー操作が発生した場合にJavaScriptコードを実行する方法です。
ハンドラを割り当てる方法はいくつかあります。最も簡単な方法から見ていきましょう。
HTML属性
on<event>
という名前の属性を使用して、HTMLでハンドラを設定できます。
たとえば、input
のclick
ハンドラを割り当てるには、ここに示すようにonclick
を使用できます。
<input value="Click me" onclick="alert('Click!')" type="button">
マウスをクリックすると、onclick
内のコードが実行されます。
onclick
内では、属性自体が二重引用符で囲まれているため、単一引用符を使用することに注意してください。属性内にあることを忘れて、このように二重引用符を内部で使用すると、onclick="alert("Click!")"
正しく動作しません。
HTML属性は多くのコードを記述するのに便利な場所ではないため、JavaScript関数を生成してそこで呼び出す方が良いでしょう。
クリックすると、関数countRabbits()
が実行されます。
<script>
function countRabbits() {
for(let i=1; i<=3; i++) {
alert("Rabbit number " + i);
}
}
</script>
<input type="button" onclick="countRabbits()" value="Count rabbits!">
ご存知のように、HTML属性名は大文字と小文字が区別されないため、ONCLICK
はonClick
やonCLICK
と同じように機能します…しかし、通常は属性は小文字で記述されます:onclick
。
DOMプロパティ
DOMプロパティon<event>
を使用してハンドラを割り当てることができます。
たとえば、elem.onclick
<input id="elem" type="button" value="Click me">
<script>
elem.onclick = function() {
alert('Thank you');
};
</script>
HTML属性を使用してハンドラが割り当てられると、ブラウザはその属性を読み取り、属性の内容から新しい関数を生成し、それをDOMプロパティに書き込みます。
そのため、この方法は実際には前の方法と同じです。
これらの2つのコードスニペットは同じように機能します。
-
HTMLのみ
<input type="button" onclick="alert('Click!')" value="Button">
-
HTML + JS
<input type="button" id="button" value="Button"> <script> button.onclick = function() { alert('Click!'); }; </script>
最初の例では、HTML属性を使用してbutton.onclick
を初期化しますが、2番目の例ではスクリプトを使用します。これが唯一の違いです。
onclick
プロパティは1つしかないため、複数のイベントハンドラを割り当てることはできません。
以下の例では、JavaScriptを使用してハンドラを追加すると、既存のハンドラが上書きされます。
<input type="button" id="elem" onclick="alert('Before')" value="Click me">
<script>
elem.onclick = function() { // overwrites the existing handler
alert('After'); // only this will be shown
};
</script>
ハンドラを削除するには、elem.onclick = null
を代入します。
要素へのアクセス:this
ハンドラ内のthis
の値は、要素です。ハンドラが設定されている要素です。
以下のコードでは、button
はthis.innerHTML
を使用してその内容を表示します。
<button onclick="alert(this.innerHTML)">Click me</button>
起こりうる間違い
イベントを使い始める場合は、いくつかの微妙な点に注意してください。
既存の関数をハンドラとして設定できます。
function sayThanks() {
alert('Thanks!');
}
elem.onclick = sayThanks;
しかし注意してください:関数はsayThanks
として割り当てる必要があり、sayThanks()
ではありません。
// right
button.onclick = sayThanks;
// wrong
button.onclick = sayThanks();
括弧を追加すると、sayThanks()
は関数呼び出しになります。そのため、最後の行は実際には関数の実行結果であるundefined
(関数は何も返さないため)を取得し、それをonclick
に割り当てます。これは機能しません。
…一方、マークアップでは括弧が必要です。
<input type="button" id="button" onclick="sayThanks()">
違いは簡単に説明できます。ブラウザが属性を読み取ると、属性の内容から本体を持つハンドラ関数を生成します。
そのため、マークアップはこのプロパティを生成します。
button.onclick = function() {
sayThanks(); // <-- the attribute content goes here
};
ハンドラにはsetAttribute
を使用しないでください。
このような呼び出しは機能しません。
// a click on <body> will generate errors,
// because attributes are always strings, function becomes a string
document.body.setAttribute('onclick', function() { alert(1) });
DOMプロパティでは大文字と小文字が区別されます。
DOMプロパティは大文字と小文字を区別するため、elem.ONCLICK
ではなくelem.onclick
にハンドラを割り当てます。
addEventListener
前述のハンドラ割り当て方法の基本的な問題は、1つのイベントに複数のハンドラを割り当てることができないことです。
たとえば、コードの一部がクリック時にボタンを強調表示し、別の部分が同じクリック時にメッセージを表示したいとします。
そのためには2つのイベントハンドラを割り当てたいのですが、新しいDOMプロパティは既存のプロパティを上書きします。
input.onclick = function() { alert(1); }
// ...
input.onclick = function() { alert(2); } // replaces the previous handler
Web標準の開発者は、長い間それを理解しており、このような制約に縛られない特別なメソッドaddEventListener
とremoveEventListener
を使用してハンドラを管理する代替方法を提案しました。
ハンドラを追加する構文
element.addEventListener(event, handler, [options]);
イベント
- イベント名、例:
"click"
。 ハンドラ
- ハンドラ関数。
オプション
- プロパティを持つ追加のオプションオブジェクト
once
:true
の場合、リスナーはトリガーされた後に自動的に削除されます。capture
:イベントを処理するフェーズ。後の章バブリングとキャプチャで説明します。歴史的な理由から、options
はfalse/true
でもかまいません。これは{capture: false/true}
と同じです。passive
:true
の場合、ハンドラはpreventDefault()
を呼び出しません。ブラウザのデフォルトアクションで後で説明します。
ハンドラを削除するには、removeEventListener
を使用します。
element.removeEventListener(event, handler, [options]);
ハンドラを削除するには、割り当てられたものとまったく同じ関数を渡す必要があります。
これは機能しません。
elem.addEventListener( "click" , () => alert('Thanks!'));
// ....
elem.removeEventListener( "click", () => alert('Thanks!'));
removeEventListener
は別の関数(同じコードを持つが、それは問題ではなく、異なる関数オブジェクトである)を取得するため、ハンドラは削除されません。
正しい方法は次のとおりです。
function handler() {
alert( 'Thanks!' );
}
input.addEventListener("click", handler);
// ....
input.removeEventListener("click", handler);
注意してください - 関数を変数に格納しないと、削除できません。addEventListener
で割り当てられたハンドラを「読み取る」方法はありません。
addEventListener
を複数回呼び出すと、次のように複数のハンドラを追加できます。
<input id="elem" type="button" value="Click me"/>
<script>
function handler1() {
alert('Thanks!');
};
function handler2() {
alert('Thanks again!');
}
elem.onclick = () => alert("Hello");
elem.addEventListener("click", handler1); // Thanks!
elem.addEventListener("click", handler2); // Thanks again!
</script>
上記の例でわかるように、DOMプロパティとaddEventListener
の両方を使用してハンドラを設定できます。しかし、一般的にはこれらの方法のいずれか1つだけを使用します。
addEventListener
でのみ機能します。DOMプロパティでは割り当てることができないイベントがあります。addEventListener
でのみ可能です。
たとえば、ドキュメントが読み込まれ、DOMが構築されたときにトリガーされるDOMContentLoaded
イベントです。
// will never run
document.onDOMContentLoaded = function() {
alert("DOM built");
};
// this way it works
document.addEventListener("DOMContentLoaded", function() {
alert("DOM built");
});
そのため、addEventListener
の方が汎用性があります。ただし、このようなイベントは例外的なものです。
イベントオブジェクト
イベントを適切に処理するには、何が起こったかについて詳しく知りたいでしょう。「クリック」や「キーダウン」だけでなく、ポインタ座標は?どのキーが押されましたか?などです。
イベントが発生すると、ブラウザはイベントオブジェクトを作成し、詳細をそこに格納し、それを引数としてハンドラに渡します。
イベントオブジェクトからポインタ座標を取得する例を次に示します。
<input type="button" value="Click me" id="elem">
<script>
elem.onclick = function(event) {
// show event type, element and coordinates of the click
alert(event.type + " at " + event.currentTarget);
alert("Coordinates: " + event.clientX + ":" + event.clientY);
};
</script>
event
オブジェクトのいくつかのプロパティ
event.type
- イベントの種類、ここでは
"click"
です。 event.currentTarget
- イベントを処理した要素。ハンドラがアロー関数でない限り、またはその
this
が他のものにバインドされていない限り、this
とまったく同じです。その場合は、event.currentTarget
から要素を取得できます。 event.clientX
/event.clientY
- ポインタイベントの場合、カーソルのウィンドウ相対座標。
さらに多くのプロパティがあります。それらの多くはイベントの種類に依存します。キーボードイベントには1つのプロパティセットがあり、ポインタイベントには別のプロパティセットがあります。さまざまなイベントの詳細に移行する際に、後でそれらを学習します。
HTMLでハンドラを割り当てると、次のようにevent
オブジェクトを使用することもできます。
<input type="button" onclick="alert(event.type)" value="Event type">
ブラウザが属性を読み取ると、function(event) { alert(event.type) }
のようなハンドラが作成されるため、可能です。つまり、その最初の引数は"event"
と呼ばれ、本体は属性から取得されます。
オブジェクトハンドラ:handleEvent
addEventListener
を使用して、関数だけでなくオブジェクトをイベントハンドラとして割り当てることができます。イベントが発生すると、そのhandleEvent
メソッドが呼び出されます。
たとえば
<button id="elem">Click me</button>
<script>
let obj = {
handleEvent(event) {
alert(event.type + " at " + event.currentTarget);
}
};
elem.addEventListener('click', obj);
</script>
ご覧のとおり、addEventListener
がハンドラとしてオブジェクトを受け取ると、イベントが発生した場合にobj.handleEvent(event)
が呼び出されます。
次のようにカスタムクラスのオブジェクトを使用することもできます。
<button id="elem">Click me</button>
<script>
class Menu {
handleEvent(event) {
switch(event.type) {
case 'mousedown':
elem.innerHTML = "Mouse button pressed";
break;
case 'mouseup':
elem.innerHTML += "...and released.";
break;
}
}
}
let menu = new Menu();
elem.addEventListener('mousedown', menu);
elem.addEventListener('mouseup', menu);
</script>
ここでは、同じオブジェクトが両方のイベントを処理します。addEventListener
を使用して、リスンするイベントを明示的に設定する必要があることに注意してください。menu
オブジェクトはここではmousedown
と mouseup
のイベントのみを取得し、他の種類のイベントは取得しません。
handleEvent
メソッドは、すべてを単独で行う必要はありません。代わりに、以下のように、イベント固有の他のメソッドを呼び出すことができます。
<button id="elem">Click me</button>
<script>
class Menu {
handleEvent(event) {
// mousedown -> onMousedown
let method = 'on' + event.type[0].toUpperCase() + event.type.slice(1);
this[method](event);
}
onMousedown() {
elem.innerHTML = "Mouse button pressed";
}
onMouseup() {
elem.innerHTML += "...and released.";
}
}
let menu = new Menu();
elem.addEventListener('mousedown', menu);
elem.addEventListener('mouseup', menu);
</script>
これで、イベントハンドラが明確に分離され、サポートが容易になる可能性があります。
概要
イベントハンドラの割り当て方法は3つあります。
- HTML属性:
onclick="..."
。 - DOMプロパティ:
elem.onclick = function
。 - メソッド: 追加するには
elem.addEventListener(event, handler[, phase])
、削除するにはremoveEventListener
。
HTML属性は控えめに使用します。HTMLタグの中にあるJavaScriptは、少し奇妙で異質に見えるからです。また、大量のコードをそこに記述することもできません。
DOMプロパティを使用しても問題ありませんが、特定のイベントのハンドラを複数割り当てることはできません。多くの場合、その制限は差し迫ったものではありません。
最後の方法は最も柔軟性がありますが、記述するのも最も長くなります。これだけで動作するイベントはいくつかあり、たとえばtransitionend
と DOMContentLoaded
(後述)があります。また、addEventListener
はオブジェクトをイベントハンドラとしてサポートしています。その場合、イベントが発生するとhandleEvent
メソッドが呼び出されます。
ハンドラをどのように割り当てても、最初の引数としてイベントオブジェクトを受け取ります。そのオブジェクトには、何が起こったかについての詳細が含まれています。
イベント全般とさまざまな種類のイベントについては、次の章で詳しく説明します。
コメント
<code>
タグを使用し、数行の場合は<pre>
タグで囲み、10行以上の場合はサンドボックス(plnkr、jsbin、codepen…)を使用してください。