3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

はじめてのアドベントカレンダーAdvent Calendar 2023

Day 2

【JavaScript】URLの変更を検知するイベントを作る

Posted at

はじめに

私はこの記事のコードの実行環境として、

  • 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);
});

// 略

参考

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?