一度だけイベントを実行したい
第三引数にonceプロパティがtrueのオブジェクトを指定
document.addEventListener('click', function() {
console.log('hello');
}, {
once: true
});
最もシンプルですが、IE、Edgeは対応していません。
イベントリスナにremoveEventListenerでそのリスナ自体を削除する
document.addEventListener('click', function foo() {
console.log('hello');
this.removeEventListener('click', foo);
});
無名関数に名前を付けないと拾えないので、名前は必須です。
レガシーブラウザにも対応しています。
イベントの伝播を制御したい
実行順を制御する
イベントは、イベントが発生した要素(イベントターゲット)から次の要素へと伝播します。
このイベントの流れをイベントフローと呼びます。
イベントフローは次の3つのフェーズに分かれています。
-
キャプチャリングフェーズ
windowオブジェクトを起点としてDOMツリーを下っていき、イベントターゲットまでのフェーズです。
親要素から子要素へと伝播します。
このフェーズで登録されたイベントリスナは、イベントターゲットのイベントリスナより先に実行されます。 -
ターゲットフェーズ
イベントターゲットに登録されたイベントリスナが実行されるフェーズです。 -
バブリングフェーズ
イベントターゲットからDOMツリーを上っていき、window
オブジェクトまでのフェーズです。
子要素から親要素へと伝播します。
このフェーズで登録されたイベントリスナは、イベントターゲットのイベントリスナより後に実行されます。
どのフェーズでイベントを実行するか制御するには、addEventListenerの第三引数(useCapture)に真偽値を設定します。
デフォルトではfalseが設定されているので省略した場合はバブリングフェーズに登録されます。
true
を設定するとキャプチャリングフェーズに登録されます。
イベントターゲットに登録されたイベントリスナはターゲットフェーズで実行されるためuseCaptureがtrueでもfalseでも関係ありません。
例
<div id="foo">foo</div>
var foo = document.getElementById('foo');
document.addEventListener('click', function() {
console.log('document');
}, false);
foo.addEventListener('click', function() {
console.log('foo');
}, false);
上記では文字列foo
をクリックすると、
foo
document
という順番で出力されます。
document
に登録されているイベントリスナをキャプチャリングフェーズで実行されるように、第三引数をtrue
にします。
var foo = document.getElementById('foo');
document.addEventListener('click', function() {
console.log('document');
}, true); // trueに変える
foo.addEventListener('click', function() {
console.log('foo');
}, false);
この状態でfoo
をクリックすると
document
foo
と出力されます。
イベントの伝播をストップする
イベントの伝播はイベントオブジェクトのstopPropagationメソッドでストップします。
先ほどの例で考えると、foo
をクリックしたときにdocument
と出力されないようにするには、foo
のクリックイベントがバブリングしないようにする必要があります。
document.addEventListener('click', function() {
console.log('document');
}, false);
foo.addEventListener('click', function(e) {
console.log('foo');
e.stopPropagation(); // イベントの伝播ストップ
}, false);
// fooをクリックした出力結果
// foo
イベントの伝播をストップし他のイベントリスナの呼び出しを防ぐ
stopImmediatePropagationメソッドを使うと、同じ要素に追加されていた他のイベントリスナの実行を防ぎます。
document.addEventListener('click', function() {
console.log('document');
}, false);
foo.addEventListener('click', function(e) {
console.log('foo1');
e.stopImmediatePropagation(); // イベントの伝播をストップし他のイベントリスナの呼び出しを防ぐ
}, false);
foo.addEventListener('click', function(e) {
console.log('foo2');
}, false);
// fooをクリックした出力結果
// foo1
※stopPropagationメソッドではfoo2
も出力されます。
デフォルトのイベントの動作をキャンセルする
preventDefault()メソッドを使います。
a
タグに対して、クリック時にページ遷移しないようにするためによく使います。
よく使うと思うのでサンプルコードは割愛。
カスタムイベントを作る
- イベントオブジェクト作成
- 作成したイベントオブジェクトを初期化(作成したイベントオブジェクトのtypeによって若干異なる)
- イベントリスナの登録
- イベント発行(dispatch)してイベントリスナ実行
1. イベントオブジェクト作成
var customEvent = document.createEvent(type);
createEvent
メソッドの引数には文字列でイベントタイプを指定します。
イベントタイプは数多くありますので下記参照。
document.createEvent - Web API インターフェイス | MDN
2. 作成したイベントオブジェクトを初期化
customEvent.initEvent(eventName, bubbles, cancelable);
引数 | 説明 |
---|---|
eventName | イベントの種類を表す文字列 |
bubbles | バブリングを行うかの真偽値 |
cancelable | イベントがキャンセル可能かの真偽値 |
イベントオブジェクト作成時にどのイベントタイプを指定したかによって初期化メソッドは変わります。
initEvent
メソッドを使えるのは、Event
やHTMLEvents
イベントタイプの時です。
3. イベントリスナの登録
element.addEventListener(eventName, function() {
// 実行するイベント
console.log('カスタムイベント実行');
}, false);
引数eventName
には、initEvent
メソッドで指定したイベントの種類を指定します。
4. イベント発行(dispatch)してイベントリスナ実行
target.dispatchEvent(customEvent);
最後にイベントターゲットの要素に対してイベント発行します。
dispatchEvent
メソッドの引数には作成したイベントオブジェクトを渡します。
イベント発行するとすぐに対応するイベントリスナが実行されます。
new Event()を使ったやり方
// イベントオブジェクト作成
var customEvent = new Event(eventName);
// イベントリスナの登録
element.addEventListener(eventName, function() {
console.log('カスタムイベント実行');
}, false);
// イベント発行
target.dispatchEvent(customEvent);
new Event()
を使うと、document.createEvent
メソッドでは必要だったinitEvent
メソッドでの処理が不要になり、ひと手間省けます。
しかしIEでは使えません。
カスタムイベントを使って、状態に変化が生じたときにイベントを実行するサンプル
カスタムイベントを使うと、ユーザーの操作と表示を切り離すことができ、オブジェクト間の関係を疎結合にすることができます。コードの拡張や修正がしやすくなります。
自分がこれまでに使った例としては、一つの変数を監視して、その変数の内容に変化が加えられたら、表示を変えるというものがあります。
それを簡単に再現してみます。
<button class="btn">100</button>
<button class="btn">200</button>
<button class="btn">300</button>
'use strict';
/**
* IE判定
*/
function isIE() {
var ua = navigator.userAgent.toLowerCase();
return ((ua.indexOf('msie') > -1) && (ua.indexOf('opera') === -1)) || (ua.indexOf('trident') > -1);
}
/**
* イベントオブジェクト作成
* @param {String} eventName
* @return {Object}
*/
function makeEvent(eventName) {
if (isIE()) {
var ev = document.createEvent('Event');
ev.initEvent(eventName, true, false);
return ev;
}
return new Event(eventName);
}
/**
* イベント発行
* @param {HTMLElement} target
* @param {Object} eventObj
*/
function fireEvent(target, eventObj) {
console.log('カスタムイベント実行!');
target.dispatchEvent(eventObj);
}
/**
* 状態管理の変数に値をセット
* @param {*} value
*/
function setData(value) {
data.push(value);
// イベント発行
fireEvent(document, showDataEvent);
}
// 状態管理用変数
var data = [];
// カスタムイベント作成
var showDataEvent = makeEvent('showData');
var btns = document.querySelectorAll('.btn');
// カスタムイベントリスナ登録
document.addEventListener('showData', function() {
console.log(data);
});
Array.prototype.slice.call(btns).forEach(function(btn) {
btn.addEventListener('click', function() {
setData(this.textContent);
});
});
変数data
に対して、setData
ファンクションを使って値をセットしたのちにカスタムイベントを実行しています。
結果的に状態に変化が生じたときにイベントを発行しているという意味合いになります。
これだけだとあまりカスタムイベントの効果が得られにくいですが、もっと規模が大きくなるとその効果は大きく感じられます。