HTMLのinput要素の値が書き換わった時に、何かの処理を行いたい場合、changeイベントで処理しますが、type属性がhiddenになっている場合、changeイベントが発火しないため、値が書き換わったら何かの処理を行うという事ができません。
iframeで読み込んでいるページの中身が変化したら処理を行いたいという場合もあるでしょう。
(私はiframeで読み込んでいるページに処理を追加できない構成でiframeの中身が変化したら親ページでの処理が必要でした)
これらは、MutationObserverを利用する事で解決します。
MutationObserver
MutationObserverはDOMが変化した時に処理を行うAPIです。
(APIと言っても何かを新たに読み込んだりする必要はありません)
Chrome, Edge, Firefox, Safari, IE11でも対応しているので安心です。
使い方
まずは、何が変化した時に処理を行うかを決めます。変化した時に処理を行いたい物をtrueにします。
- childList
- attributes
- characterData
- subtree
- attributeOldValue
- characterDataOldValue
- attributeFilter
実装
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>