はじめに
以前にShopify開発パートナーさんから、ある機能を追加してみたが動作しない、というご相談を受けました。Shopifyの構造自体よくわかっていないということもあり、勉強がてら解析してみました。どうやら目的の機能を追加するには動的に生成された要素に対して既存のスクリプトをあてる必要があることがわかりました。
このような動的に追加されたり削除されたりする要素(例えば、スクリプト中でappendChild
したり、removeChild
することは多々あると思います。私はよく行ないます。)に対して既存のスクリプトをあてる場合には、MutationObserver
が有効1 です。今回はその備忘録です。
MutationObserverとは
MutationObserver とは、指定したコールバック関数を DOM の変更時に実行させる API です。この API は、DOM3 Events の仕様で定義されていた Mutation Events を新しく設計し直したものです。
指定した要素の監視役ですね。変更があった場合、条件に合致すれば任意の処理を実行します。今回は機能追加したい要素の親要素の監視をします。
デモ
ボタンクリックで動的に生成される<div id="id3">
に対して、以下のスクリプトをあてたいと思います。
const showAlert = () => alert('clicked')
前提となるHTMLとJavascript。
<div id="container">
<input type="button" id="btn_genDiv" value="click">
<!-- ここにid0~id9のdivを生成 -->
</div>
window.onload = () => {
const showAlert = () => alert('clicked')
const genDiv = e => {
const container = document.querySelector('#container')
if (container.querySelectorAll('[id^=div]').length) return
for (let i = 0; i < 10; i++) {
let div = document.createElement('div')
container.appendChild(div)
div.id = `div${i}`
div.innerText = i
}
}
// ボタンクリクでdiv要素を生成
const btn = document.querySelector('#btn_genDiv')
btn.addEventListener('click', genDiv, false)
/* 以降省略 */
}
動作しないコード
window.onload = () => {
/* 省略 */
const div3 = document.querySelector('#div3')
div3.addEventListener('click', showAlert, false)
}
そもそも当該の要素がwindow.onload
の処理時にDOMtreeに含まれていない2 のでUncaught TypeError
となります。
動作するコード
window.onload = () => {
/* 省略 */
const target = document.querySelector('#container') // スクリプト設定対象の親要素を設定
const config = {childList: true, subtree: true} // 監視条件の設定
//親要素に変化があった場合の処理設定
const observer = new MutationObserver(() => {
const div3 = document.querySelector('#div3')
div3.addEventListener('click', showAlert, false)
})
// 監視開始
observer.observe(target, config)
}
ボタンクリックでDIV要素が展開され、特定の要素<div id="id3">
がクリックされると意図通りアラートダイアログが表示されます。
configの設定について
プロパティについてはMDNにて確認できますが、以下に引用します。
プロパティ | 意味 |
---|---|
childList | 対象ノードの子ノード(テキストノードも含む)に対する追加・削除を監視する場合は true にします。 |
attributes | 対象ノードの属性に対する変更を監視する場合は true にします。 |
characterData | 対象ノードのデータに対する変更を監視する場合は true にします。 |
subtree | 対象ノードとその子孫ノードに対する変更を監視する場合は true にします。 |
attributeOldValue | 対象ノードの変更前の属性値を記録する場合は true にします(attributes が true の時に有効)。 |
characterDataOldValue | 対象ノードの変更前のデータを記録する場合は true にします(characterData が true の時に有効)。 |
attributeFilter | すべての属性の変更を監視する必要がない場合は、(名前空間を除いた)属性ローカル名の配列を指定します。 |
注意点としてchildList
、attributes
、characterData
のいずれか1つ、かつ値をtrue
に設定する必要があります。
今回は子ノードのみの監視ですが、子孫ノードも含める場合はsubtree: true
を追加すれば良いです。
結果
See the Pen MutationObserver Sample by STSHISHO (@STSHISHO) on CodePen.
最後に
サンプルのため特定の要素<div id="id3">
が確実に読み込まれることを前提として記述していますので、追加されることが不確実な場合は存在判定が必要になるでしょう。
JSFiddleでもサンプル公開中です。