JSでレンダリングしてaddEventListenerする方法について投稿した際、@megmismさんからMutationObserverでも実現可能と助言をいただきました。
恥ずかしながらわたくしMutationObserver初耳でした、、、
ということで今回は、練習も兼ねて「JSでレンダリングした要素にclickイベントを付与する」という以前の記事と同様のシチュエーションをこのMutationObserverで実現してみたいと思います。
「MutationObserverって何?」となった自分のようなJS初学者の方向けです。
MutationObserverの基本
MutationObserver とは、指定したコールバック関数を DOM の変更時に実行させる API です。
と記載されています。MutationOberverを使用することで、特定のhtml要素を監視し、変更が生じた際に何かしらの処理を実行することが可能になるわけですね。
基本的な使い方は以下のようになります。
// observerインスタンスを作成
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
// DOMの変更時に行いたい処理
})
})
// 監視対象のhtml要素を取得
const observedEl = document.querySelector('#observed')
// 監視する際の設定値
const config = { attributes: true, childList: true, characterData: true };
// 監視対象と設定を引数に渡して、監視を開始
observer.observe(observedEl, config)
詳しい使用法については、以下の記事が参考になると思います。非常にわかりやすいのでおすすめです。
- [JavaScriptのMutationObserverでDOMの変化を監視する方法] (JavaScriptのMutationObserverでDOMの変化を監視する方法)
new MutationObserver((mutations) => {})
//mutations = MutationRecordの配列
MutationObserverのコールバック関数の第一引数には MutationRecord の配列が入ります。
MDNのドキュメントを参照しますと、
MutationRecord とは、DOM に生じた個々の変更を表すオブジェクトです。このオブジェクトは MutationObserver のコールバック関数に渡されます。
とあります。つまり、このMutationRecordオブジェクトを操作することでDOMの変更に関する情報を取得することができます。
今回やったこと
- 監視対象のdivを用意
- 監視対象のdivにDOM変更時の処理を書く
- 監視を開始する
- 監視対象のdivに要素を追加していく
1. 監視対象のdivを用意
<div id="observed">
<h1>observed div</h1>
</div>
id='observed'とした監視対象のdivを用意します。
2. 監視対象のdivにDOM変更時の処理を書く
監視対象のdivに変更が生じた際に実行したい処理は、
- 追加された要素がid='target'だった場合,
- clickイベントを追加する (クリックしたらalertする単純なもの)
です。この処理を実現する関数addClickEvent()
を実装します。
const addClickEvent = (mutation) => {
// 監視対象のdivに追加された要素を配列で取得
const addedNodes = Array.from(mutation.addedNodes)
// clickイベントの付与対象となる要素を取得
const targetNodes = addedNodes.filter((addedNode) => addedNode.id === 'target')
// 対象となる要素が存在しなければ終了
if (targetNodes.length === 0) return
targetNodes.forEach((targetNode) => {
// clickイベントを付与
targetNode.addEventListener('click', (e) => {
alert('clicked !!!!!')
})
})
}
順をおって説明しますと、
// 監視対象のdivに追加された要素を配列で取得
const addedNodes = Array.from(mutation.addedNodes)
MutationRecord.addedNodes
には、監視対象のDOMに追加された要素がNodeList
で格納されています。(arrayではない!) この後、idでフィルタリングをしたいのでArray.from()を使用して、このNodeListをarrayに変換しておきます。
// clickイベントの付与対象となる要素を取得
const targetNodes = addedNodes.filter((addedNode) => addedNode.id === 'target')
追加された要素の中からidが'target'のものを取得します。
// 対象となる要素が存在しなければ終了
if (targetNodes.length === 0) return
targetNodes.forEach((targetNode) => {
// clickイベントを付与
targetNode.addEventListener('click', (e) => {
alert('clicked !!!!!')
})
})
idが'target'の要素が存在した場合に、それらにクリックイベントを付与しています。
これで監視対象のDOMに変更が生じた際の処理が完成です。
##3. 監視を開始する
// observerインスタンスを作成
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
addClickEvent(mutation)
})
})
// 監視対象のdivを取得
const observedEl = document.querySelector('#observed')
// 監視の設定
const config = { childList: true }
// 監視開始
observer.observe(observedEl, config)
MutationObserverのインスタンスを作成します。コンストラクタの引数に渡すコールバック関数内で、DOMに変更が生じた際に、先ほど実装したaddClickEvent()
を呼び出します。
監視対象と設定値を引数に渡してMutationObserver.observe()
を呼び出し、監視を開始します。今回の監視対象はdiv#observed
ですので、この要素を取得し、設定値config
と共にobserve
に渡しています。設定値に関しては、必要最低限のものだけに絞ってます。
childList : 対象ノードの子ノード(テキストノードも含む)に対する追加・削除を監視する場合は true にします。
今回のケースでは子ノードの追加を監視したいだけなので { childList: true }
のみとしています。その他、設定値の詳細に関してはこちらをどうぞ。
4. 監視対象のdivに要素を追加していく
新しい要素を作成するgenerateElement()
を用意します。
const generateElement = ({ tag = 'p', textContent, className, id }) => {
const newEl = document.createElement(tag)
if (textContent) newEl.textContent = textContent
if (className) newEl.classList.add(className)
if (id) newEl.id = id
return newEl
}
generateElement()
は引数に渡したタグ名、テキスト、クラス名、id名をもつhtml要素を作成し、返します。デフォルトでpタグを返すようにしてあります。
id="target"のターゲット要素を監視対象のdivに追加する関数も用意します。
const renderTarget = () => {
// clickイベント付与対象の要素を作成
const newEl = generateElement({
textContent: 'target element',
className: 'target',
id: 'target'
})
// ターゲット要素を監視対象のdivに追加
document.querySelector('#observed').append(newEl)
}
(cssのためにclassも付与していますが、今回の本筋とは関係ありません。)
id="target"の要素にのみclickイベントが付与されているか確認するため、ダミーの要素をdivに追加する関数も用意しておきます。
const renderDummy = () => {
// dummyの要素を作成
const newEl = generateElement({
textContent: 'dummy element',
className: 'dummy',
id: 'dummy'
})
// ダミー要素を監視対象のdivに追加
document.querySelector('#observed').append(newEl)
}
これでレンダリングする準備が整いました。
htmlを調整
わかりやすくするために要素追加用の作成し、ボタンを押したら監視対象のdivに要素が追加されるようにします。
ダミー要素、ターゲット要素それぞれを追加するボタンを作成します。
<div id="observed">
<h1>observed div</h1>
<button id="generate-dummy">generate dummy</button>
<button id="generate-target">generate target</button>
</div>
generate dummyボタンをクリックしたらrenderDummy()
でダミー要素をレンダリングし、
// generate dummy ボタンをクリックしたらダミー要素を追加
document.querySelector('#generate-dummy').addEventListener('click', (e) => {
renderDummy()
})
generate dummyボタンをクリックしたらrenderTarget()
でターゲット要素をレンダリングします。
// generate target ボタンをクリックしたらターゲット要素を追加
document.querySelector('#generate-target').addEventListener('click', (e) => {
renderTarget()
})
動作確認
これで完成です。きちんと動作しているか確認します。
追加したターゲット要素にのみclickイベントが付与されていますので、OKです。
まとめ
MutationObserverを初めて使用してみましたが、DOMの変更をより細かいレベルで監視・制御する際に重宝しそうです。
MutationRecordオブジェクトを使用すれば、属性値や削除されたノードの情報、変更前の値など様々な情報を取得できますし、MutationObserverのconfigもより細かく設定することが可能です。今回の例で触れていない項目がいくつもあるのでもう少し遊んでみたいと思います。
#参考リンク
- MutationObserver : MDNドキュメント
- MutationRecord : MDN ドキュメント
- [JavaScriptのMutationObserverでDOMの変化を監視する方法] (JavaScriptのMutationObserverでDOMの変化を監視する方法)