JavaScript
event
Vanilla.JS

addEventListener(removeEventListener、useCapture、stopPropagationなど)・カスタムイベント使い方 簡単なまとめ

More than 1 year has passed since last update.

一度だけイベントを実行したい

第三引数に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でも関係ありません。

html
<div id="foo">foo</div>
js
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にします。

js
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タグに対して、クリック時にページ遷移しないようにするためによく使います。
よく使うと思うのでサンプルコードは割愛。

カスタムイベントを作る

  1. イベントオブジェクト作成
  2. 作成したイベントオブジェクトを初期化(作成したイベントオブジェクトのtypeによって若干異なる)
  3. イベントリスナの登録
  4. イベント発行(dispatch)してイベントリスナ実行

1. イベントオブジェクト作成

var customEvent = document.createEvent(type);

createEventメソッドの引数には文字列でイベントタイプを指定します。
イベントタイプは数多くありますので下記参照。
document.createEvent - Web API インターフェイス | MDN

2. 作成したイベントオブジェクトを初期化

customEvent.initEvent(eventName, bubbles, cancelable);
引数 説明
eventName イベントの種類を表す文字列
bubbles バブリングを行うかの真偽値
cancelable イベントがキャンセル可能かの真偽値

イベントオブジェクト作成時にどのイベントタイプを指定したかによって初期化メソッドは変わります。
initEventメソッドを使えるのは、EventHTMLEventsイベントタイプの時です。

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では使えません

カスタムイベントを使って、状態に変化が生じたときにイベントを実行するサンプル

カスタムイベントを使うと、ユーザーの操作と表示を切り離すことができ、オブジェクト間の関係を疎結合にすることができます。コードの拡張や修正がしやすくなります。
自分がこれまでに使った例としては、一つの変数を監視して、その変数の内容に変化が加えられたら、表示を変えるというものがあります。
それを簡単に再現してみます。

html
<button class="btn">100</button>
<button class="btn">200</button>
<button class="btn">300</button>
js
'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ファンクションを使って値をセットしたのちにカスタムイベントを実行しています。
結果的に状態に変化が生じたときにイベントを発行しているという意味合いになります。
これだけだとあまりカスタムイベントの効果が得られにくいですが、もっと規模が大きくなるとその効果は大きく感じられます。

参考