I have a simple MutationObserver
setup as a test. The HTML has a span
whose text content gets updated once per second (and a div
for messages):
<span class="tester"></span>
<div id="msg"></div>
The MutationObserver is set to watch .tester
and writes text to the #msg
div
when it observes a change. Meanwhile, a setInterval()
runs once/second to change the text in .tester
:
var target = document.querySelector('.tester');
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log(mutation.type);
$('#msg').append(mutation.type+"<br/>")
setTimeout(function() { $('#msg').text(''); }, 500);
});
});
var config = { attributes: true, childList: true, characterData: true };
observer.observe(target, config);
setInterval(function() {
$('#msg').text('');
$('.tester').text("update: "+Math.random())
}, 1000);
I would expect this code to print once per second that the characterData has changed. According to Mozilla's docs for MutationObserver, it says about characterData: "Set to true if mutations to target's data are to be observed." Instead, I see no characterData mutations but do see two childList mutations every second.
Why am I not seeing any characterData mutations, and why am I seeing two childList mutations?
Here's a working example with CodePen.
Overview. MutationObserver is a Web API provided by modern browsers for detecting changes in the DOM. With this API one can listen to newly added or removed nodes, attribute changes or changes in the text content of text nodes.
childList – changes in the direct children of node , subtree – in all descendants of node , attributes – attributes of node , attributeFilter – an array of attribute names, to observe only selected ones. characterData – whether to observe node.
The MutationObserver interface provides the ability to watch for changes being made to the DOM tree. It is designed as a replacement for the older Mutation Events feature, which was part of the DOM3 Events specification.
This is done by setting the characterData property to true in the options object. Here's the code: let options = { characterData: true }, observer = new MutationObserver(mCallback); function mCallback(mutations) { for (let mutation of mutations) { if (mutation. type === 'characterData') { // Do something here... } } }
The reason is as Jeremy Banks said: When you use jQuery's text()
, it removes all the text nodes and then adds in new ones. That's not a change to character data, it's a change to the childList
: Removing the node that's there and replacing it with a new one.
To see a change to character data, you have to modify the existing text node's nodeValue
, and observe subtree
modifications:
var target = document.querySelector('.tester');
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log(mutation.type);
$('#msg').append(mutation.type+"<br/>")
setTimeout(function() { $('#msg').text(''); }, 500);
});
});
var config = {
attributes: true,
childList: true,
characterData: true,
subtree: true // <=== Change, added subtree
};
observer.observe(target, config);
var timer = setInterval(function() {
$('#msg').text('');
// Change --VVVVV modifying the existing child node
$('.tester')[0].firstChild.nodeValue = "updated" + Math.random();
}, 1000);
// Stop after 10 seconds
setTimeout(function() {
clearInterval(timer);
}, 10000);
<span class="tester">x</span><!-- Change, added a starting child node -->
<div id="msg"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
Re your question about why there are two childList
mutations, yes I think you're right: They're removing the child, then adding a new one. If we use the replaceChild
method, we see only a single mutation:
var target = document.querySelector('.tester');
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log(mutation.type);
$('#msg').append(mutation.type+"<br/>")
setTimeout(function() { $('#msg').text(''); }, 500);
});
});
var config = {
attributes: true,
childList: true,
characterData: true,
subtree: true // <=== Change, added subtree
};
observer.observe(target, config);
var timer = setInterval(function() {
$('#msg').text('');
// Change --VVVVV modifying the existing child node
var text = document.createTextNode("updated" + Math.random());
var parent = $('.tester')[0];
parent.replaceChild(text, parent.firstChild);
}, 1000);
// Stop after 10 seconds
setTimeout(function() {
clearInterval(timer);
}, 10000);
<span class="tester">x</span><!-- Change, added a starting child node -->
<div id="msg"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With