LoginSignup
2
2

More than 3 years have passed since last update.

Javascriptでchageイベントの取れないhidden項目やiframeの変化をMutationObserverで取得し処理する

Last updated at Posted at 2020-05-30

HTMLのinput要素の値が書き換わった時に、何かの処理を行いたい場合、changeイベントで処理しますが、type属性がhiddenになっている場合、changeイベントが発火しないため、値が書き換わったら何かの処理を行うという事ができません。

iframeで読み込んでいるページの中身が変化したら処理を行いたいという場合もあるでしょう。
(私はiframeで読み込んでいるページに処理を追加できない構成でiframeの中身が変化したら親ページでの処理が必要でした)

これらは、MutationObserverを利用する事で解決します。

MutationObserver

MutationObserverはDOMが変化した時に処理を行うAPIです。
(APIと言っても何かを新たに読み込んだりする必要はありません)
Chrome, Edge, Firefox, Safari, IE11でも対応しているので安心です。

MDN MutationObserver

使い方

まずは、何が変化した時に処理を行うかを決めます。変化した時に処理を行いたい物をtrueにします。

  • childList
  • attributes
  • characterData
  • subtree
  • attributeOldValue
  • characterDataOldValue
  • attributeFilter

詳細:MutationObserverInit

実装

hidden項目の場合

今回は、Addボタンを押したらcounterの値が+1ずつ追加され、counterの数が5で割り切れる時に、Rankが1つ上がる。Rank上限は5
という処理を作ってみます。(実際はAddボタンを押した時に処理すればいいですが...)

<input type="hidden" name="counter" value="0">
<input type="button" name="add" value="Add">
<p>Rank: <input type="number" name="rank" value="1" readonly></p>

要素の定義

counter: クリックするたびに+1されるinputを定義
rank: ランクの値を表示するinputを定義

const counter = document.querySelector('[name=counter]');
const rank = document.querySelector('[name=rank]');

MutationObserverの設定

指定したDOM要素に変更があった時に行う処理です。
mutationsには、変更されたDOM要素が配列で入って来るためループし、変更された属性(attributeName)がvalueの時がinputの値が変わった時なので、続きの処理を行っています。

const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
        rank.value = Math.floor(parseInt(mutation.target.value, 10) / 5) + 1;
        if (rank.value == 5) observer.disconnect();
    });
});

rankの最大値を5にしているので、もしもrankの値が5に変わったら、DOMの監視を終了させています。
DOMの監視が不要になったら終了しましょう。

observer.disconnect();

このconfigでtrueを指定した物が、監視対象となります。
今回はvalue値の変化だけを監視するので、属性の変化を監視するattributesをtrueにし、attributeFilterにて監視るる属性をvalueに限定させています。

監視対象をなるべく絞り込んで置く事で

const config = {
    attributes: true,
    attributeFilter: ['value']
};

そして、監視を開始します。

observer.observe(counter, config);

以上をまとめた物がこちらです。

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8" />
        <title>Rank</title>
    </head>
    <body>
        <input type="hidden" name="counter" value="0">
        <input type="button" name="add" value="Add">
        <p>Rank: <input type="number" name="rank" value="1" readonly></p>
        <script>
            (() => {
                'use strict';
                const counter = document.querySelector('[name=counter]');
                const rank = document.querySelector('[name=rank]');

                const observer = new MutationObserver((mutations) => {
                    mutations.forEach((mutation) => {
                        rank.value = Math.floor(parseInt(mutation.target.value, 10) / 5) + 1;
                        if (rank.value == 5) observer.disconnect();
                    });
                });
                const config = {
                    attributes: true,
                    attributeFilter: ['value']
                };
                observer.observe(counter, config);

                document.querySelector('[name=add]').addEventListener('click', () => {
                    counter.value = parseInt(counter.value, 10) + 1;
                });
            })();
        </script>
    </body>
</html>

valueの値が書き換わっていない時

document.querySelector('[name=hoge]').value = "fuga";

もしも、[name=hoge]の値変化を監視していて、上記のように値をセットした時、
画面上ではinputの値が変化しているのに、DOMが書き換わっておらず、MutationObserverで変更処理が走らない場合があります。

その時は、このようにsetAttributeを使う事でDOMが変化し、変更処理が走るようになります。

document.querySelector('[name=hgoe]').setAttribute('value', 'fuga');

iframeの中身の変化

iframeの中身が変わった時の処理はjQueryを利用すると楽に実装できます。
iframeのbodyの中身が変化した時の処理ですが、iframeの読み込みが終わるのをsetIntervalで待ってから、処理を行っています。

<iframe src="#" id="frame"></iframe>
<script>
    const frameLoading = () => {
            setInterval(() => {
            if ($('#frame').contents().find('body') != 'undefined') {
                clearInterval(frameLoading);
                const target = $('#frame').contents().find('body');
                const config = {
                    attributes: true,
                    characterData: true,
                    childList: true,
                    subtree: true
                };
                new MutationObserver((mutations) => {
                    // do something.
                    console.log(mutations);
                }).observe(target, config);
            }
        }, 100);
    };
    frameLoading();
</script>
2
2
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
2
2