こんにちは。2年目に突入した新卒フロントエンドエンジニアです。
使用率は高い割に、
「なんかボタン押す時とか画面のリサイズ・ロードするときに使うやつでしょー」
くらいの知識で使っていた addEventListener() メソッドについて向き合うことにしました。
基本
const button = document.querySelector('.button');
button.addEventListener('click', () => {
/* ボタンをクリックした時の処理 */
});
基本的な addEventListener() の使い方です。window
, document
, body
だけでなくあらゆる要素にイベント処理を登録することができます。死ぬほどよく見ますね。
登録したイベント情報は開発ツールで確認できます。写真はChromeのEvent Listenersタブです。
on~とaddEventlistener
JavaScriptでイベントを登録する方法にはもう一つ、on~
がありますが、on~
は一つのターゲットに複数のイベントが登録することができず、2つ目のon~
イベントを登録すると上書きされてしまいます。
const button = document.querySelector('.button');
button.onclick = () => {
console.log('clicked from on*');
};
button.onclick = () => {
console.log('clicked again from on*');
};
// log): clicked again from on*
buttonをクリックしても2回目のリスナーしか呼び出されません。
addEventListener() は複数登録することができます(実行は登録順)。
const input = document.querySelector('.input');
// 関数化して複数登録(イベントターゲットが同じ時のみ)
const eventTypes = ['focus', 'input', 'submit'];
const entryAddEventListenerMulti = (target, types, handler, useCapture) => {
for (let type of types) {
target.addEventListener(type, handler, useCapture);
}
};
entryAddEventListenerMulti(input, eventTypes, (event) => {
/* input要素への処理 */
});
基本的には addEventListener() を使って古のブラウザ対応をしないといけない時などにon*を使うのがいいと思います。
eventを一回だけ実行したいとき
第3引数onceを指定することでeventを1回実行した後削除してくれます。
const button = document.querySelector('.button');
button.addEventListener('click', () => {
alert('clicked!');
}, {once: true});
event.preventDefault()
event.preventDefault()
を使用してターゲットのデフォルトの動作(<a>
タグのリンク遷移・フォームの送信など)を防止することができます。
button.addEventListener('click', (event) => {
event.preventDefault();
});
イベントハンドラ内のthis
イベントハンドラ内のthis
はイベントターゲット要素を参照します。
const button = document.querySelector('.button');
button.addEventListener('click', function() {
console.log(this);
});
// log): <button class="button" type="button"></button>
アロー関数式ではevent.currentTarget
プロパティで同じようにイベントターゲットを参照することができます。
const button = document.querySelector('.button');
button.addEventListener('click', (event) => {
console.log(event.currentTarget);
});
// log): <button class="button" type="button"></button>
bind
を使えばthis
の参照先を指定できます。
const data = {
type: 'hoge',
name: 'hoge',
method: function() {}
};
const handleClick = function() {
console.log(this);
};
button.addEventListener('click', handleClick.bind(data));
// log): {type: 'hoge', name: 'hoge', method: ƒ} (Chrome Dev Tools)
thisの値はdataオブジェクトを参照します。
イベント伝達フェーズについて
イベント処理の伝達にはフェーズがあって、Windowからターゲットへ下りていくキャプチャリングフェーズ(1)、ターゲットに到達した時のターゲットフェーズ(2)、ターゲットからWindowに向けて上がっていくバブリングフェーズ(3)の3段階になっています。急に難しいです。
通常、イベントの実行はバブリングフェーズで、イベントのターゲットからWindowに向けて上がっていきながら実行されます。なので基本的にはこんな仕組みなんだなーと思っておけば問題ないと思います。
([出典]JAVASCRIPT.INFO 現代の JavaScript チュートリアル -バブリング と キャプチャリング: https://ja.javascript.info/bubbling-and-capturing)
const input = document.querySelector('.input');
document.body.addEventListener('input', () => {
console.log('input from body');
});
input.addEventListener('input', () => {
console.log('input from input');
});
window.addEventListener('input', () => {
console.log('input from window');
});
document.addEventListener('input', () => {
console.log('input from document');
});
/* log:) input from input
* input from body
* input from document
* input from window */
記述順に関係なくinput→body→document→windowの順に実行されます。
キャプチャリングフェーズでイベントを実行したいとき
イベントリスナーの第3引数capture
を指定してキャプチャリングフェーズでイベントを実行することもできます。
windowなどの祖先要素のイベントを優先的に発火することができますが、イベント処理順の予測が難しくなってしまうので基本的には使用しない方がいいと思います。
document.addEventListener('input', () => {
console.log('input from document');
}, {capture: true});
// trueだけでも指定できる
document.addEventListener('input', () => {
console.log('input from document');
}, true);
☝🏻 もともとaddEventListenerにはuseCapture
オプションしか無かったが新たにオプションが追加されたため後方互換性のためにtrue
のみでもuseCapture
オプションが指定できるようになっているみたいです。
event.eventPhaseプロパティ
event.eventPhase
プロパティで現在のフェーズを確認することもできます
document.addEventListener('input', (event) => {
console.log(event.eventPhase);
}, {capture: true});
// log): 1
キャプチャリング= 1, ターゲット= 2, バブリング= 3 が返されます。
passiveによる性能の改善
preventDefault()をしていないときに、passiveオプションをtrueにすることでスクロールの処理性能の低下を防ぐことができます。
window.addEventListener('scroll', () => {
/* スクロール時の処理 */
}, {passive: true});
※ Safari 以外のブラウザーでは、Window
、Document
、Document.body
に対する wheel
、mousewheel
、touchstart
、touchmove
イベントのpassive
オプションの既定値が true になっているようです。
主要なイベント一覧
よく使うイベントをざっとまとめてみました。
マウス
イベント | 発生条件 |
---|---|
mousedown / mouseup | 要素上でマウスボタンがクリックされた / 離されたとき |
mouseover / mouseout | マウスポイントが要素に来る / 出ていったとき |
mousemove | 要素上でのマウス移動毎に発生 |
※pointerdown / pointerup | ポインターがアクティブ / 非アクティブになったとき |
※pointerover / pointerout | ポインターが境界内 / 境界外に移動したとき |
※pointermove | 要素上でのポインター移動毎に発生 |
click | mousedownイベントの後に発生 |
☝🏻 pointer
系イベントはmouse
系イベントを継承していて、タッチペンや画面を直接タッチして操作するデバイスにも対応しているので、マウス系イベント実装の際はpointer
系にしてしまって大丈夫そうです。(MDN)
キーボード
イベント | 発生条件 |
---|---|
keydown | キーを押したとき(長押しの場合は自動的に繰り返される) |
keyup | キーを離したとき |
スクロール
イベント | 発生条件 |
---|---|
scroll | ビューまたは要素がスクロールされたとき |
wheel | ユーザーがポインティングデバイス (通常はマウス) のホイールボタンを回転させたとき |
scrollend | 要素のスクロールが完了したとき |
☝🏻 scrollend
イベントは実験的な機能でChrome Canary、Chrome115以降、Firefox109以降のブラウザのみ対応しています。(Can I use)…2023/5/7時点
スクロールが完全に終了したことを検知できるので、スムーススクロールの終了を待ってDOM要素を操作したい時などに便利そうです。早く使えるようになってほしいですね…
フォーム関連
イベント | 発生条件 |
---|---|
focus | 要素がフォーカスを受け取ったとき |
blur | 要素がフォーカスを失ったとき |
focusin | 要素がフォーカスを受け取ろうとしているとき focusとの主な違いはバブリングを行わないこと |
focusout | 要素がフォーカスを失おうとしているとき blurとの主な違いはバブリングを行わないこと |
change |
<input> , <select> , <textarea> 要素において、ユーザーによる要素の値の変更が確定したときテキストの場合はフォーカスが外れたら発生する |
input |
<input> , <select> , <textarea> の各要素の値 (value) が変更されたときchangeとは違い入力があったら即座に発生する |
submit | フォームが送信されたとき |
読み込み
イベント | 条件 |
---|---|
load | ブラウザがすべてのリソース(画像, スタイルなど)を読み込んだとき |
DOMContentLoaded | ブラウザがHTMLを完全に読み込み、DOMツリーが構築されたとき |
beforeunload / unload | ユーザがページを離れようとしているとき |
その他のイベント
イベント | 発生条件 |
---|---|
cut / copy / paste | ClipboardEventクラスに属しており、コピー/ペーストされるデータへのアクセスを提供する |
visibilitychange | タブのコンテンツが表示状態または非表示状態になったときにdocument に発生 |
fullscreenchange | ブラウザーが全画面モードに移行したり終了したりした直後に発生 |
resize | ビュー (ウィンドウ) の大きさが変更されたとき windowオブジェクトでのみ発行される |
hashchange | URL のフラグメント識別子 (URLの#記号で始まり続く部分) が変化したときに発生します。 |
他にもモバイルデバイス用のtouch
系イベント(Safari非対応🥲)、ユーザーのドラッグを判定するdrag
系イベント、CSSアニメーションの実行に合わせて発生するanimation
系イベント…などなど多くのイベントが用意されています。
W3C UI Events
カスタムイベントの作成
Event / CustomEventで独自のイベントを作成することもできます。
作成したイベントはdispatchEvent()
で実行する必要があります。
const myEvent = new Event('my-event');
document.dispatchEvent(myEvent);
カスタムイベントの特徴
自作のイベントは下記のような特徴を持っています
- 自作のイベントは
event.isTrusted
プロパティの値がfalseになる(本来のイベントはtrue) -
event.bubbles
プロパティの値がデフォルトでfalseになっている(バブリングしない) -
event.cancelable
プロパティの値がデフォルトでfalseになっている(preventDefaultできない) -
event.detail
に追加プロパティを設定できる↓
// detailに追加プロパティを設定
document.addEventListener('my-event', (event) => {
console.log(event.detail.message);
});
const myEvent = new CustomEvent('my-event', {
bubbles: true,
cancelable: true,
detail: {
message: 'This is Custom Event'
}
});
document.dispatchEvent(myEvent);
// log): This is Custom Event
また上記のようにbubbles: trueを指定してバブリングさせる、cancelable: trueを指定してpreventDefault()を有効にすることもできます。
Event / CustomEventの代わりにMouseEvent
・KeyboardEvent
・FocusEvent
などを使用してコンストラクタを作成できます。これらを使うことでコンストラクタの作成時にそれぞれのイベントの標準プロパティを指定することができます。
デフォルトのイベント’click’
や’keydown’
などと同じ名前のイベントを作成することもできますが、その際は注意が必要です。
参考記事
最後に参考にした記事を載せます。見ていただいてありがとうございました🙇🏻♂️