LoginSignup
3
0

More than 1 year has passed since last update.

[Javascript] 動的に追加・削除される要素に対してスクリプトをあてるにはMutationObserverを使う

Last updated at Posted at 2021-03-05

はじめに

以前に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 すべての属性の変更を監視する必要がない場合は、(名前空間を除いた)属性ローカル名の配列を指定します。

注意点としてchildListattributescharacterDataのいずれか1つ、かつ値をtrueに設定する必要があります。

今回は子ノードのみの監視ですが、子孫ノードも含める場合はsubtree: trueを追加すれば良いです。

結果

See the Pen MutationObserver Sample by STSHISHO (@STSHISHO) on CodePen.

最後に

サンプルのため特定の要素<div id="id3">が確実に読み込まれることを前提として記述していますので、追加されることが不確実な場合は存在判定が必要になるでしょう。

JSFiddleでもサンプル公開中です。

[参考]

  1. 実案件では直接、管理画面でのコード編集ができない状況でした。Shopifyに限らず、CMSのプラグイン等で有効かと思いますが、機能追加によって生じる関連部分への影響をしっかりと検証することは必要ですね。

  2. display: none;visibility: hidden;からの表示・非表示切り替えの場合、window.onloadの処理時にはDOMtreeに含まれているためエラーになりません。

3
0
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
0