How to make one MutationObserver listen for changes on multiple elements?

Viewed 896

I have implemented the following code for observing changes in class:

$(document).ready(function () {
var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    if (mutation.attributeName === "class") {
      var attributeValue = $(mutation.target).prop(mutation.attributeName);
      console.log("Class attribute changed to:", attributeValue);
    }
  });
});

observer.observe(document.querySelector(".xxx"), {
  attributes: true
});  });

JSFiddle link: https://jsfiddle.net/8Lfj0ev3/

Ideally, I want this to listen for changes in all elements with class 'xxx'. But it is only listening to first element with xxx class and ignoring others. How can I fix this?

1 Answers

Two ways to address this:

Multiple observe calls

You can call observe multiple times to watch multiple elements:

$(".xxx").each(function() {
  observer.observe(this, {
    attributes: true
  });
});

Live Example:

$(document).ready(function() {
  var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
      if (mutation.attributeName === "class") {
        var attributeValue = $(mutation.target).prop(mutation.attributeName);
        console.log("Class attribute changed to:", attributeValue);
      }
    });
  });
  
  $(".xxx").each(function() {
    observer.observe(this, {
      attributes: true
    });
  });
});

setTimeout(function() {
  $(".d1").addClass("x1");
}, 800);
setTimeout(function() {
  $(".d2").addClass("x2");
}, 1600);
setTimeout(function() {
  $(".d3").addClass("x3");
}, 2400);
<div class="d1 xxx"></div>

<div class="d2 xxx"> </div>

<div class="d3 xxx"> </div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Watch container's subtree

But another option is to watch the subtree of the nearest container they're all in and then only pay attention to changes if they occurred within a .xxx element:

var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    if (mutation.attributeName === "class") {
      var $target = $(mutation.target);                            // ***
      if ($target.hasClass("xxx")) {                               // ***
        var attributeValue = $target.prop(mutation.attributeName);
        console.log("Class attribute changed to:", attributeValue);
      }
    }
  });
});

observer.observe(document.body, {                                  // ***
  attributes: true,
  subtree: true                                                    // ***
});

Live Example:

$(document).ready(function() {
  var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
      if (mutation.attributeName === "class") {
        var $target = $(mutation.target);
        if ($target.hasClass("xxx")) {
          var attributeValue = $target.prop(mutation.attributeName);
          console.log("Class attribute changed to:", attributeValue);
        }
      }
    });
  });
  
  observer.observe(document.body, {
    attributes: true,
    subtree: true
  });
});

setTimeout(function() {
  $(".d1").addClass("x1");
}, 800);
setTimeout(function() {
  $(".d2").addClass("x2");
}, 1600);
setTimeout(function() {
  $(".d3").addClass("x3");
}, 2400);
<div class="d1 xxx"></div>

<div class="d2 xxx"> </div>

<div class="d3 xxx"> </div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

In that example the nearest container is document.body, but of course in your real situation there may be something closer to the elements.

Related