JavaScript を使ってるとイベント関係で次のトラブルに悩まさること多いです。
- addEventListenrでリスナー登録
- removeEventListenerで解除する
- アレ?なぜか削除が効いてない...?
ここでは個人的にハマってしまった、
JSでの removeEventListenerが効かない・消えないときの対処法 を紹介!
特に jQuery とかが使えない環境だと、イベントリスナーには要注意です。
このページの目次
なぜ? removeEventListener が効かない問題に遭遇
ごくまれに jQuery とかライブラリが一切使えない場合・・・
そのときはイベントリスナー登録に次の2つを使わないといけなくなります。
- addEventListener
イベントリスナーを追加する。上書きではなく新規追加
- removeEventListener
イベントリスナーを削除する。追加したリスナー関数を渡す必要あり
問題なのは removeEventListenr を使ってイベントを削除する場合です。
そもそもこの関数、次みたいなものだと説明されています。
▼ MDNでの removeEventListener の解説
EventTarget.removeEventListener() メソッドは、 EventTarget から、以前に EventTarget.addEventListener() で登録されたイベントリスナーを削除します。削除されるイベントリスナーはイベントの型、イベントリスナー関数そのもの、マッチングプロセスに影響を与えるさまざまな任意のオプションを使用して識別します。
上の説明の EventTarget とはすなわちクリックとかされる要素のこと
例えばこの関数の簡単な使い方は次みたいな感じです。
▼ すっごく単純化したコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
const btnAlert = document.querySelector('.alert'); /// クリックリスナー登録 btnAlert.addEventListener('click', showAlert); /// リスナーのコールバック関数 function showAlert(){ alert('Button is clicked'); } /// 5秒後にクリックリスナーを消す setTimeout(function(){ btnAlert.removeEventListener('click', showAlert); }, 3000); |
このコード自体は何の問題もなく動きます。でも複雑なコードだったり、 addEventListener の設定次第では期待通りに動かないことがあるのが厄介ですね。
特に僕個人がハマってしまったのは次の2パターン
原因1.登録時と削除時の useCapture の値が違うから
まず1つめの原因はコレ
リスナー登録時と削除時の useCapture の値が違うから
この useCapture はイベントの伝播のされ方を変えるフラグのことです。
- useCaputure : trueの場合
イベント発生元の一番上位の要素 => イベント元要素 の順にイベントが伝わる
- useCapture : falseの場合
イベント発生元 => イベント発生元の上位要素 の順にイベントが伝わる
デフォルト値は false なので、この順番を気にしてる人は少ないかもしれません。
でも removeEventListener を使う時は、この useCapture の値が問題になります。
問題になるのは 登録時の useCapture の値と、削除時の値が違うときです。
▼ 例えば分かりやすいコードで示すなら以下の通り
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/// クリックリスナー登録。useCaptureは true に設定 btnAlert.addEventListener('click', showAlert, true); /// リスナーのコールバック関数 function showAlert(){ alert('Button is clicked'); } /// 5秒後にリスナー除去 setTimeout(function(){ /// NG : addEventListener の useCapture と一致してない btnAlert.removeEventListener('click', showAlert, false); }, 5000); |
ハイライトした部分に注目
こういう風に useCapture の値が一致してないとイベント削除が全く効かなくなるみたいです。
なので useCapture の値を一致させればOK
▼ こういうコードに直してみた
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/// クリックリスナー登録。useCaptureは true に設定 btnAlert.addEventListener('click', showAlert, true); /// リスナーのコールバック関数 function showAlert(){ alert('Button is clicked'); } /// 5秒後にリスナー除去 setTimeout(function(){ /// OK : addEventListener のuseCapture と一致してる btnAlert.removeEventListener('click', showAlert, true); }, 5000); |
※ 2021/05/08 : コメントのご指摘があり、間違いを修正。
これでちゃんとイベント削除が働くようになりました。
他人のコードを読んだり書いたりするときは、useCapture の値に注意ですね。
原因2.アロー関数(無名関数)を渡してしまってるから
もう1つ原因があって、
それがアロー関数を直接渡してしまってるから
そもそも removeEventListener はイベントハンドラーを指定することで削除ができます。なので無名関数を渡しても削除はできないという訳です。
たとえば removeEventListener が効かないのはこんな場合です。
▼ クリックリスナーが消えないBADコード例
1 2 3 4 5 6 7 8 9 10 11 12 |
/// アロー関数でクリックリスナー登録 btnAlert.addEventListener('click', (e) => { alert('Button is clicked'); }); /// 5秒後にリスナー除去 setTimeout(function(){ /// アロー関数を直接渡してしまっている btnAlert.removeEventListener('click', (e) => { alert('Button is clicked'); }); }, 5000); |
こういうコードもNG
アロー関数の時点で addEventListener に渡したものと全く別のインスタンスができるので、いくら removeEventListenr を呼び出しても無駄になります。
対処法としてはアロー関数を変数として保存すればOK
▼ 改善したコード例
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/// リスナーコールバック(アロー関数) var showAlert = (e) => { alert('Button is clicked'); } /// クリックリスナー登録 btnAlert.addEventListener('click', showAlert); /// 5秒後にリスナー除去 setTimeout(function(){ /// 保存しておいたアロー関数を渡せば解決 btnAlert.removeEventListener('click', showAlert); }, 5000); |
2~4行目の部分がアロー関数を保存しているコード
とにかく素の関数とかアロー関数を渡さないことが大事です。
ここまでのまとめ
最後に removeEventListener が効かない原因(対処法)まとめ
- 削除時の useCapture の値が違うから
これはイベント削除が動かない原因でありがち。もし追加時に true を設定しているなら削除時も true を渡す必要あり
- アロー関数を渡してしまってるから
削除するコールバック関数は追加時と削除時で一致させないとダメ。なので関数オブジェクトをそのまま渡すのはNG
以上、 removeEventListener で消えない・効かないときの対処法でした。