JavaScript入門 イベント
JavaScriptでは何らかのトリガーでブラウザから発生した通知をイベントとして受け取ります。イベントには、クリック操作やスクロール操作、フォームへの入力などのユーザー起因のイベントや、画面ロードの完了などのブラウザ起因のイベントがあります。
JavaScriptでイベントの発生を検知し、何らかの処理を実行するには、イベントが発生したときに実行される関数を、イベントハンドラまたはイベントリスナに登録します。
JavaScriptでイベントを登録するには、イベントハンドラにアクションを登録する方法とイベントリスナにアクションを登録する方法があります。
イベントハンドラ
イベントハンドラの登録
イベントハンドラとは、イベントが発生したときに実行される関数のことです。Windowオブジェクト(window)、Elementオブジェクト、Documentオブジェクト(document)などのonから始まるプロパティに対してアクションを設定することによって、イベント発生時に任意の処理を実行できます。
構文:イベントハンドラの登録方法
EventTarget.on{イベントタイプ} = action;
- EventTarget:この要素でイベントが発生したときに、登録された関数が実行されます。Elementオブジェクトやwindows、documentなどが使用可能です。
- on{ イベントタイプ }:イベントタイプにはclickなどが入ります。たとえば、クリックイベントの場合はonclickになります。
- action:イベント発生時に実行したい関数を登録します。名前付き関数または無名関数、アロー関数を登録できます。また、actionの第1引数には、Eventオブジェクが渡されます。
イベントハンドラはonから始まり、その後にイベントタイプが続きます。
クリック操作を検知するイベントハンドを登録
<div>
<button id ="minus"> - </button>
<span id="number">0</span>
<button id="plus">+</button>
</div>
<script>
//数値の初期化
let count = 0;
//Elementの取得
const number = document.querySelector("#number");
const plusBth = document.querySelector("#plus");
const minusBtn = document.querySelector("#minus");
plusBth.onclick = function (event){ //#plusボタンのクリック時のアクションを定義
count++; //countに1を加算
number.textContent = count; //#numberのテキストを更新
};
minusBtn.onclick = function (event){ //#minusボタンのクリック時のアクションを定義
count--; //countに1を減算
number.textContent = count; //#numberのテキストを更新
};
</script>
なお、イベントが発生することを発火と言います。上記のコードでは、ボタンをクリックしたときにonclickが発火します。[+]ボタンをクリックすると、plusBtn.onclickに登録された関数が実行され、1加算された値が画面上に表示されます。[-]ボタンをクリックしたときは、1減算された値が同様に画面上に表示されます。また、このとき、関数の引数のeventに渡されるのがEventオブジェクトです。
イベントハンドラの解除
イベントハンドラをnullで初期化することによって、登録したアクションを解除できます。
構文:イベントハンドラの解除方法
EventTarget.on{イベントタイプ} = null;
ボタンに登録したアクションを解除
<button>アラート</button>
<script>
const btn = document.querySelector( "button );
btn.onclick = function() {
alert( "アラート" );
}
btn.onclick = null;
</script>
イベントリスナ
イベントリスナとは、イベントにアクションをひも付ける仕組みのことです。イベントが発火するとイベントリストに登録されたアクションが実行される点はイベントハンドラと同じですが、次の3点がことなります。
イベントリスナがイベントハンドラと異なる点
- オプションの指定が可能:アクションが実行される条件をオプションで指定できる。
- 複数のアクション(関数)の登録が可能:複数のアクションを個別に登録できる
- アクションごとに登録解除が可能:複数のアクションを個別に解除できる
イベントリスナの登録
イベントリストに対してアクションを登録するには、「addEventListenerメソッド」を使います。
構文:イベントリストとしてアクション(関数)を登録
EventTarget.addEventListener( "イベントタイプ" , action [, options] );
EventTarget:この要素でイベントが発生したときに、登録された関数(action)が実行されます。Elementオブジェクトやwindow、documentなどが使用可能です。
”イベントタイプ”:イベントタイプを文字列で入力します。onは先頭につきません。たとえば、クリックイベントの場合にclickとなります。
action:イベント発生時に実行したい関数を登録します。名前付き関数または無名関数、アロー関数を登録できます。また、actionの第1引数にはEventオブジェクトが渡されます。
options:optionsでは、イベントリスナの挙動の設定を行うことができます。真偽値またはオブジェクトを設定可能です。
- 真偽値のtrueが設定された場合:{ capture: true }と設定した場合と同じ意味になる
- 次のプロパティを使用できる。
プロパティ | 説明 |
---|---|
capture | 登録されたアクションをキャプチャリングフェーズで実行する。デフォルトはfalse |
once | アクションの実行を一度きりとする場合にtrueを渡す。実行後は自動的にアクションは削除される。デフォルトはfalse |
passive | trueとした場合にはパッシブリスナが有効になる。パッシブリスナが有効な状態では、アクション内で呼び出される「Event.preventDefault」はブラウザのデフォルト処理を停止しない。 |
例:イベントリスナにアクションを登録
<body>
<div>
<button id ="minus"> - </button>
<span id="number">0</span>
<button id="plus">+</button>
</div>
<script>
//数値の初期化
let count = 0;
//Elementの取得
const number = document.querySelector("#number");
const plusBth = document.querySelector("#plus");
const minusBtn = document.querySelector("#minus");
plusBth.addEventListener("click",function(event) { //クリックイベントにアクションを登録
count++; //countに1を加算
number.textContent = count; //#numberのテキストを更新
});
minusBtn.addEventListener("click",function(event) { //クリックイベントにアクションを登録
count--; //countに1を減算
number.textContent = count; //#numberのテキストを更新
});
</script>
</body>
先ほどのイベントハンドラのときと同様、ボタンをクリックすることで、画面上の数値が変更されるようになります。
複数のイベントリスナを登録
イベントリスナを使うと、「複数のアクションをイベントに紐づけることができます。」次の例では、#all-color要素に対して、colorChangeとbgColorChangeの2つのアクションをclickイベントに登録しています。なお、イベントハンドラ(onclick)に直接登録するときには関数を1つしか登録できません。
例:複数のアクションをイベントリスナとして登録
<body>
<div>この要素の色が変わります</div>
<button id="color">文字色の変更</button>
<button id="bg-color">背景色の変更</button>
<button id="all-color">文字色と背景色の変更</button>
<button id="reset-color">リセット</button>
<script>
//数値の初期化
let count = 0;
//Elementの取得
const targetEl = document.querySelector("div");
const colorBtn = document.querySelector("#color");
const bgColorBtn = document.querySelector("#bg-color");
const allColorBtn = document.querySelector("#all-color");
const resetBtn = document.querySelector("#reset-color");
//文字色を変更するアクション
function colorChange( event ){
targetEl.style.color = "#ff0000"; //文字色を赤色に変更
}
//背景色を変更するアクション
function bgColorChange( event ){
targetEl.style.backgroundColor = "blue"; //背景色を青色に変更
}
//スタイルをリセットするアクション
function reset( event ){
targetEl.style.backgroundColor = "";
targetEl.style.color = "";
}
//イベントリスナにアクションを登録
colorBtn.addEventListener( "click" , colorChange );
bgColorBtn.addEventListener( "click" , bgColorChange );
allColorBtn.addEventListener( "click" , colorChange );
allColorBtn.addEventListener( "click" , bgColorChange );
//スタイルのリセット
resetBtn.addEventListener( "click" , reset );
</script>
</body>
上記実装すると、「文字色の変更ボタン」をクリックすると、文字の色が変更され、「背景色の変更ボタン」をクリックすると、背景色が変更になります。「文字色と背景色の変更ボタン」を押すと、文字色と背景色が変わります。「リセットボタン」でテキストのスタイルが初期化されます。
イベントリスナの解除
removeEventListenerメソッドを使うことで、登録したアクションの解除を行うことができます。
構文:removeEventListenerの記法
EventTarget.removeEventListener( "イベントタイプ", action );
- EventTarget:addEventListenerでイベントリスナの登録時に渡した要素と同じ要素を指定します。
- action:addEventListenerでイベントリスナの登録時に渡したのと同じ関数への参照を渡すことで、その関数をイベントリスナの対象から除外します。
注意点
removeEventListenerの第2引数には、addEventListenerに渡した関数と同じ参照保持した関数を渡す必要がある点です。
例:アクションの解除が成功しないコード
<body>
<div>この要素の色は変わりません。</div>
<button id="color">文字色の変更</button>
<script>
const targetEl = document.querySelector("div");
const colorBtn = document.querySelector("#color");
colorBtn.addEventListener( "click" , function( event ){ //アクションの登録:無名関数A
targetEl.style.color = "#ff0000";
} );
colorBtn.removeEventListener( "click" , function( event ){ //アクションの解除:無名関数B
targetEl.style.color = "#ff0000";
} );
</script>
</body>
上記の例では、removeEventListenerに渡している関数(無名関数B)がaddEventListenerに渡している関数(無名関数A)と同じ記述のため、うまくいきません。この場合、あくまで同じ機能を持った関数を渡しているだけなので、別のメモリ空間に登録された関数となります。removeEventListenerに渡す関数は、addEventListenerに登録された関数と「同じ参照」を保持している必要があります。
例:アクションの解除が成功するコード
<body>
<div>この要素の色は変わりません。</div>
<button id="color">文字色の変更</button>
<script>
const targetEl = document.querySelector("div");
const colorBtn = document.querySelector("#color");
function colorChange( event ){
targetEl.style.color = "#ff0000";
}
colorBtn.addEventListener( "click" , colorChange ); //アクションの登録
colorBtn.removeEventListener( "click" , colorChange ); //アクションの解除
</script>
</body>
上記コードでは、イベント登録時と解除時で同じ関数への参照が取得できるため、イベントリスナに登録したアクションの解除を行うことができます。
HTMLの属性にアクションを登録する方法
非推奨な方法になりますが、HTMLの属性値としてアクションを設定することもできます。この方法は、HTMLファイル内にJavaScriptコードを記述するため、プロジェクトが大きくなってくると、コードを整理するのが困難になってきます。
HTML属性にアクションを登録
<button onclick="alert( 'ボタンがクリックされました。' );">ボタン</button>
イベントの伝播
ページ内のどこかの要素でイベントが発生した場合には、「イベントが要素間を伝播していきます」。これをイベントの伝播と呼びます。イベントの伝播は、「キャプチャリングフェーズ」、「ターゲットフェーズ」、「バブリングフェーズ」の3つのフェーズに分けることができます。HTML内の要素でイベントが発生した場合には、これらの3つのフェーズが順番に実行されることになります。
キャプチャリングフェーズ(CapturingPhase)
イベントの伝播で最初に発生するフェーズです。キャプチャリングフェーズでは、発生したイベントと同じタイプのイベントが上位から下位へ伝播していきます。具体的には、HTML内のいずれかの要素でイベントが発生した場合には、最上位のWindowオブジェクトからイベントの伝播が始まり、Documentオブジェクト、<html>要素の順でイベントが発生した要素まで同じイベントが順番に伝わっていきます。
なお、イベントハンドラやイベントリスナで登録したアクションは、キャプチャリングフェーズでは一般には実行されません。イベントリスナのオプションで{ capture: true }を設定したアクションのみ、このフェーズで実行されます。また、このフェーズでは、まだイベントが発火した要素に対してイベントの伝播が到達していないため、<button>要素に登録したアクションはまだ実行されません。キャプチャリングに続く次のターゲットフェーズで<button>要素に登録されたアクションが実行されます。
例:キャプチャリングの確認
<body>
<div id="div1"></div>
<div id="div2">
<button>ボタン</button>
</div>
<script>
//Elementの取得
const div1 = document.querySelector("#div1");
const div2 = document.querySelector("#div2");
const button = document.querySelector("button");
//windowにclickイベントを登録
window.addEventListener( "click", () => {
console.log( "windowsのclickイベント" );
}, { capture: true });
//div1にclickイベントを登録
div1.addEventListener( "click", () => {
console.log( "div1のclickイベント" );
}, { capture: true });
//div2にclickイベントを登録
div2.addEventListener( "click", () => {
console.log( "div2のclickイベント" );
}, { capture: true });
//buttonにclickイベントを登録
button.addEventListener( "click", () => {
console.log( "buttonのclickイベント" );
}, { capture: true });
</script>
</body>
上記のコードでは、windowオブジェクト、#div1要素、#div2要素、<button>要素のclickイベントにアクションをそれぞれ追加しています。また、イベントリスナのオプションには{ capture: true }を設定しているため、キャプチャリングフェーズでアクションが実行されるように設定されています。なお、windowオブジェクトに対するclickイベントは、画面全体に適用されます。画面上のボタンをクリックすると、上記の実行悔過のようなログが出力されます。この順番を確認してみるとwindow→div2→buttonの順でイベントが発火しています。これは、{ capture: true }によって、キャプチャリングフェーズでアクションが実行されているためです。また、#div1要素に関しては<button>要素の祖先要素ではないため、#div要素のイベントは<button>をクリックしても発火しません。
ターゲットフェーズ(Target Phase)
キャプチャリングフェーズによって、イベントの伝播がイベントが発生した要素までたどり着くと、ターゲットフェーズになります。このフェーズでは、イベントが発生した要素に登録されているアクションを実行します。
イベントの伝播は、アクションが登録されていない要素をクリックした場合にも発生します。そのため、ターゲット要素(イベントが発生した要素)にアクションが登録されていない場合には、ターゲットフェーズでは特に何も起こりません。
バブリングフェーズ(Bubbling Phase)
ターゲットフェーズが完了すると、今度はイベントが上位の親要素、祖先要素に対してイベントの伝播が起こります。これをバブリングフェーズと言います。
バブリングフェーズでは、親要素、祖先要素、document、windowで同じイベントタイプに登録されているアクションが実行されます。また、イベントリスナに{ capture: true }が設定されているアクションは、キャプチャリングフェーズですでに実行済みのため、実行されません。イベントリスナに{ capture: true }を付けない場合には、バブリングによってイベント伝播によるアクションの実行を行います。
例:バブリングの確認
<body>
<div id="div1"></div>
<div id="div2">
<button>ボタン</button>
</div>
<script>
//Elementの取得
const div1 = document.querySelector("#div1");
const div2 = document.querySelector("#div2");
const button = document.querySelector("button");
//windowにclickイベントを登録(capture: trueとして登録)
window.addEventListener( "click", () => {
console.log( "windowsのclickイベント" );
}, { capture: true });
//div2にclickイベントを登録
div2.addEventListener( "click", () => {
console.log( "div2のclickイベント" );
});
//buttonにclickイベントを登録
button.addEventListener( "click", () => {
console.log( "buttonのclickイベント" );
});
</script>
</body>
上記のコードでは、windowのみ、{ capture: true }のオプションを付与しているため、windowのclickイベントのみ、キャプチャリングフェーズで実行されます。そのため、<button>要素をクリックした場合には、window→button→div2の順番にアクションが実行されます。なお、一部のイベントタイプ(たとえば、mouseleaveやfocus)では、バブリングは発生しません。しかし、ほどんどのイベントでは、バブリングが発生します。また、バブリングが発生しているかはEventオブジェクトのbubblesプロパティをtrueかどうかで判定でき、bubblesがtrueの場合はバブリングが発生します。なお、バブリングが発生しないイベントの場合には、ターゲットフェーズでイベント伝播が終了します。
キャプチャリングとバブリングが分かれている理由
最初のJavaScriptが実装されたNetScape社のブラウザでは、もともとキャプチャリングによるイベントの伝播が提唱されていました。一方、その後に参入したMicrosoft社のIEブラウザでは、バブリングによるイベントの伝播を推進していました。NetScape社のブラウザは後に開発が凍結されてしまいますが、IE9+や現代の主要ブラウザはこのような歴史を経てキャプチャリングフェーズによるイベントの検知もできるようになっています。
Eventオブジェクト
イベントハンドラやイベントリスナに登録した関数の引数に渡されるEventオブジェクトについて解説します。
構文:Eventオブジェクト
EventTarget.on{ イベントタイプ } = function( event ) {...}
EventTarget.addEventListener( "イベントタイプ", function( event ) {...} );
event:Eventオブジェクト。より正確に言うと、このEventオブジェクトは、Eventコンストラクタを継承した別のコンストラクタからインスタンス化されたオブジェクトです。たとえば、inputイベントなどに登録したアクションには、Eventコンストラクタを継承したInputEventコンストラクタから生成されたオブジェクトが渡されます。そのため、イベントタイプによってアクセス可能なプロパティが変わります。
Eventオブジェクトには、「イベントの発生状況に関わる情報が格納されています」。Eventオブジェクトに設定されるプロパティはイベントタイプによって変わります。ここでは、どのイベントタイプでも使用できる共通のプロパティとメソッドを解説します。
Eventオブジェクトのメソッド
メソッド | 説明 |
---|---|
preventDefault() | 「ブラウザのデフォルト処理の実行を抑止する」。抑止可能なイベントタイプはcancelableがtrueのイベント |
stopPropagation() | キャプチャリング、バブリングによる「イベント伝播を抑止」する。 |
stopImmediatePropagation() | stopPropagation()の作用に加え、自要素に対して複数アクションが登録されている場合には、後続のアクションの実行を抑止 |
Eventオブジェクトのプロパティ
プロパティ | 説明 | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
type | イベントタイプが文字列で渡される | ||||||||||
cancelable | prevenDefault()を使って、デフォルト処理をキャンセル可能かどうかを返す(true/false)trueの場合、キャンセル可能 | ||||||||||
bubbles | 発生中のイベントでバブリングが発生するかどうかを返す(true/false)。trueの場合、バブリングが発生する。 | ||||||||||
currentTarget | アクションを登録した(ターゲット)要素を返す。キャプチャリングやバブリングが発生しているときも、常にアクションを登録した要素を返す | ||||||||||
target | 実際にイベントが発生した要素を返す。キャプチャリングやバブリングの作用によって、実際にイベントが発生した要素とアクションが登録されている要素が異なる可能性がある。 | ||||||||||
defaultPrevented | preventDefault()によって、ブラウザのデフォルト処理がキャンセルされたかどうかを表す(true/false)。trueの場合、キャンセルされた状態を表す。 | ||||||||||
eventPhase | 実行中のイベントフェーズを表す
| ||||||||||
timestamp | ドキュメントの生成からイベントが発生までの時間を返す | ||||||||||
isTrusted | イベントがユーザー操作によって発生したものか、スクリプトによって発生したものか判定する(true/false)。ユーザー操作によって発生した場合は、trueを返す。 |
※すべて読み込み専用プロパティのため、値変更は不可