はじめに
私はこの記事のコードの実行環境として、
- Chrome バージョン: 119.0.6045.105
を使っています。
上の実行環境以外でこの記事に書いたコードが動くかどうかはわかりません。
個人用として考えたものなので...
それでもよければどうぞ。
カスタムイベントについて
JavaScriptにはCustomEvent
というものがあります。
そして、これを使うと自分でイベントを作ることができます。
基本的な構文は、
-
Element.dispatchEvent(new CustomEvent('イベント名'));
でイベントを発火 -
Element.addEventListener(処理)
でイベントを受け取る(?)
です。
今回はurlChange
というイベントを作ります。
DOMの変更を検知する
JavascriptでURLの変更を検知したいなら、DOMの変化を検知しろ。
という記事があるのですが、ここでもこの方法を使います。
基本的にURLが変わる時って、何かしら要素に変化があったりします。
一生while(true)
やsetInterval
で変更を検知し続けるのはまずそうだし、
何よりスマートじゃないです。
やり方
上の記事でも使っていますが、ここでもMutationObserver
を使います。
MutationObserver
を使うと、指定した要素に変更が加えられたときに
何かしらの処理をすることができます。
変更とみなされるのは下の通りです。
- 子要素の追加や削除(
childList
) - 属性の変化(
attributes
) - テキストノードの変化(
characterData
)
ただ、このうちのどれを検知するかは自分で指定することができます。
使い方は下の通りです。
const observer = new MutationObserver(mutations => {
// ここにDOM変更時の処理を書く
});
// 検知を開始
observer.observe(検知対象の要素, options);
// 検知を終了
observer.disconnect();
また、observe
のoptionsに使えるプロパティは以下の通りです。
observer.observe(target, {
// デフォルトだと全部false
// なんちゃらOldValueがtrueのとき、そのなんちゃらもtrueになる
subtree: true, // 子孫ノードの変更を検知するか
childList: true, // 子ノードの追加や削除を検知するか
attributes: true, // 属性の変化を検知するか
attributeFilter: ['class'], // 指定した場合、ここに入れた属性のみ検知対象になる
attributeOldValue: true, // 変化前の値を mutations.oldValue に格納するか
characterData: true, // テキストノードの変更を検知するか
characterDataOldValue: true // 変化前の値を mutations.oldValue に格納するか
// 手元で試した所、subtreeがtrueになってないと動かなかった
});
結構細かく設定できます。
実際に検知してみる
ということで、実際にDOMの変更を検知してみます。
const observer = new MutationObserver(() => {
// ここにDOM変更時の処理を書く
console.log('変更を検知');
});
observer.observe(target, options); // ここをどうするか
まず、URLが変わったときにどこのDOMが変わるかはわからないため、
targetはdocument.body
に、そしてoptionsは{subtree: true}
にします。
const observer = new MutationObserver(() => {
// ここにDOM変更時の処理を書く
console.log('変更を検知');
});
- observer.observe(target, options); // ここをどうするか
+ observer.observe(document.body, {subtree: true}); // 変更
そして、検知する対象ですが、個人的には全部検知でいいと思います。
まあ検知対象は多い方が精度高そうだし...多分...
const observer = new MutationObserver(() => {
// ここにDOM変更時の処理を書く
console.log('変更を検知');
});
observer.observe(document.body, {
subtree: true,
+ childList: true,
+ attributes: true,
+ characterData: true
});
ただ、コードの実行タイミングによってはこれだとdocument.body
が読み取れずにエラーが出るため、window.addEventListener
を使います。
const observer = new MutationObserver(() => {
// ここにDOM変更時の処理を書く
console.log('変更を検知');
});
+ window.addEventListener('DOMContentLoaded', () => {
observer.observe(document.body, {
subtree: true,
childList: true,
attributes: true,
characterData: true
});
+ });
この状態でもう検知できるはずです。
何かしらDOMを変更したときにログに変更を検知
と出ていれば成功です。
ちなみに、読み込み時にもこのログは出るかもしれません。
urlChangeイベントを作る
new MutationObserver
に入れた関数を変更して、
window
にイベントを発火するようにします。
const observer = new MutationObserver(() => {
- console.log('変更を検知');
+ window.dispatchEvent(new CustomEvent('urlChange')); // イベントを発火
});
window.addEventListener('DOMContentLoaded', () => {
observer.observe(document.body, {
subtree: true,
childList: true,
attributes: true,
characterData: true
});
});
+ // 検知のテスト用
+ window.addEventListener('urlChange', () => {
+ console.log('イベントの発火を検知');
+ });
DOMを変更したときに、無事にログが出ていれば成功です。
ただ、今のままだとURLが変わっていなくてもイベントが発火してしまうので、
URLが変わった時だけ発火するよう少し変更します。
+ let oldUrl = ''; // URLの記憶用
const observer = new MutationObserver(() => {
+ if(oldUrl !== location.href) {
window.dispatchEvent(new CustomEvent('urlChange')); // イベントを発火
+ oldUrl = location.href; // oldUrlを更新
+ }
});
window.addEventListener('DOMContentLoaded', () => {
// ~略~
});
// 検知のテスト用
window.addEventListener('urlChange', () => {
console.log('イベントの発火を検知');
});
DOMとURLを変更したときに、無事にログが出ていれば成功です。
同じページ内に飛ぶaタグ(クリックしたらDOMも変わる)なんかを使うと、
ちゃんと動くかテストすることができます。
無事に動いていることが確認できたら、// 検知のテスト用
以降のコードは消してもらってかまいません。
// 略
});
- // 検知のテスト用
- window.addEventListener('urlChange', () => {
- console.log('イベントの発火を検知');
- });
また、たまにoldUrl !== location.href
を判定するタイミングが早すぎてうまく動かないことがあるかもしれません。
その場合はsetTimeout
なんかを使って判定のタイミングを遅らせてください。
// 以下は試してません
const observer = new MutationObserver(() => {
setTimeout(() => { // 遅延
if(oldUrl !== location.href) {
window.dispatchEvent(new CustomEvent('urlChange')); // イベントを発火
oldUrl = location.href; // oldUrlを更新
}
}, 50);
});
// 略
参考