Ole Albers
Ole Albers

Reputation: 9305

Can MutationObserver make changes just right before mutation?

I need to prevent DOM-Change using mutationobserver.

I had (past) the following code to prevent specific changes:

document.bind("DOMSubtreeModified", function() {
   document.find('.Xx.xJ:Contains("wham")').closest("[jsmodel='XNmfOc']").hide();
});

Because of performance reasons I did not want to check the complete document on any dom-change but only added contents so I changed to this (now):

 var observer = new MutationObserver(function (mutations) {
        mutations.forEach(function (mutation) {
            [].slice.call(mutation.addedNodes).forEach(function (addedNode) {
               StartFilter(addedNode);                    

            });
        });
    });

    observer.observe(document, {
        childList: true,
        subtree:true,
        characterData:true,
        attributes:true        
    });

function StartFilter(newNode) {
   $(newNode).find('.Xx.xJ:Contains("wham")').closest("[jsmodel='XNmfOc']").hide();
}

But this does not really work. My guess is that "newNode" is not really a reference to the DOM-Element. (The selector is valid, "$(newNode).find('.Xx.xJ:Contains("wham")').closest("[jsmodel='XNmfOc']")" returns an element).

I did not find any method/property to reject a dom-change in MutationObserver. Is there a way to achieve what I want WITHOUT checking the whole document each time?

Upvotes: 7

Views: 6485

Answers (2)

frank-dspeed
frank-dspeed

Reputation: 1112

Since the existing answers lack clarity, here’s a more detailed explanation:

MutationObserver captures DOM changes (mutations) before the paint event occurs, meaning that these changes are observed before the animation frame. This timing is critical because it allows you to intercept and adjust modifications to the DOM before they are rendered on the screen.

With MutationObserver, you can:

  • Observe attribute changes and prevent them from being visually rendered.
  • Re-add removed elements or remove added elements in the DOM, effectively undoing these changes before they become visible.
  • Rollback other types of mutations, giving you control over visual changes at a fine-grained level.

This behavior enables modifications to be managed or blocked before any visual impact, which can be useful in scenarios where you want to manage DOM stability, limit unwanted changes, or defer heavy layout operations.

For further reading and more on how the paint pipeline works, see the Chromium documentation:
Chromium Paint Stages

Hope this helps clarify!

Upvotes: 0

Palpatim
Palpatim

Reputation: 9272

It's unclear to me whether the NodeList returned by a mutation observer is "live", in the sense that changes to nodes in that list are immediately be reflected on the DOM. But that doesn't matter, since you're only using it to create jQuery wrapped set. The basic code you've got above works as intended (see simplified snippet below), which implies that there's something else preventing your hide() call from working as expected.

My best understanding is that you can't intercept and prevent changes to the DOM--the MutationObserver is fired after the associated mutation has already occurred. That means you're not interrupting or intercepting the mutation, but rather reacting to it. In your case, that could lead to unexpected "flashing" behavior as nodes are added and then removed. A better solution in that case would be to style newly-added nodes to be hidden by default, and then add a class/style to either display them or remove them from the DOM in the mutation observer filter.

var container = document.querySelector('.container');
var addNodeButton = document.querySelector('#add');
var addNodeWithHideButton = document.querySelector('#addWithHide');

var makeAddNode = function(includeHide) {
  return function() {
    var p = document.createElement('p');
    var s = document.createElement('span');
    var msg = 'Appended at ' + new Date().getTime();
    if (includeHide) {
      msg += ' (hide)';
    }
    var t = document.createTextNode(msg);
    s.appendChild(t);
    p.appendChild(s);
    container.appendChild(p);
    console.log('appended::', p);
  };
};

var makeNode = makeAddNode(false);
var makeNodeWithHidden = makeAddNode(true);

addNodeButton.addEventListener('click', makeNode);
addNodeWithHideButton.addEventListener('click', makeNodeWithHidden);

var toArray = function() {
  return [].slice.call(arguments);
};

var observer = new MutationObserver(function (mutations) {
  mutations.forEach(function (mutation) {
    toArray(mutation.addedNodes).forEach(function (addedNode) {
      StartFilter(addedNode);
    });
  });
});

observer.observe(document, {
  childList: true,
  subtree:true,
  characterData:true,
  attributes:true
});

function StartFilter(newNode) {
  var $n = $(newNode);
  console.log('$n::', $n);
  $n.find('span:contains(hide)').fadeOut(1500);
  //   $(newNode).find('.Xx.xJ:Contains("wham")').closest("[jsmodel='XNmfOc']").hide();
}
.container {
  width: 80%;
  border: 1px solid green;
  padding: 1rem;
  margin: 1rem auto;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<button id="add">Add node without "hide"</button>
<button id="addWithHide">Add node with "hide"</button>
<div class="container"></div>

Upvotes: 5

Related Questions