Many internal pages are watermarked by employees to prevent them from taking screenshots. I used to think that the watermark was just a simple mask, and students with a little technical knowledge could delete the mask node to achieve no watermark, but after the actual operation, THEY found that they could not delete it anyway. Then began to study its principle, and then led to today’s protagonist – MutationObserver

concept

The MutationObserver interface provides the ability to monitor changes made to the DOM tree. It is designed as a replacement for the older Mutation Events feature, which is part of the DOM3 Events specification.

The Mutation Observer API is used to monitor DOM changes. The API is notified of any changes to the DOM, such as the addition or subtraction of nodes, changes in attributes, and changes in text content.

Conceptually, it is close to an event and can be understood as triggering a Mutation Observer event when a DOM change occurs. However, it differs from events in one essential way: events are synchronously triggered, meaning that changes to the DOM immediately trigger the corresponding event; Mutation Observers are triggered asynchronously. DOM changes are not triggered immediately, but not until all current DOM operations have completed.

The constructor

To do this, you first create an observer instance using the MutationObserver constructor and specify the instance’s callback function.

const observer = new MutationObserver(callback);
Copy the code

The callback function in the above code is called after each DOM change. This callback takes two arguments, the first is a mutable array and the second is an observer instance. Here is an example.

const observer = new MutationObserver((mutations, observer) = > {
  mutations.forEach((mutation) = > {
    console.log(mutation);
  });
});

Copy the code

methods

  • observe()

    The observe method is used to start listening and takes two arguments.

  • The first parameter: what you want to observeDOMnode
  • Second argument: a configuration object that specifies the specific change to observe
const article = document.querySelector('article');

const  options = {
  'childList': true.'attributes':true}; observer.observe(article, options);Copy the code

In the code above, the Observe method takes two arguments: the DOM element to observe is an article, and the type of change (child node change and attribute change) to observe.

There are several types of DOM changes that can be observed by the observer (that is, the options object in the code above).

  • ChildList: Boolean value representing changes (additions, deletions, or changes) of child nodes.

  • Attributes: Boolean value representing changes to an attribute.

  • CharacterData: Boolean value representing changes in node content or node text.

  • Subtree: Boolean value indicating whether the observer is applied to all descendants of the node.

  • AttributeOldValue: Boolean value that indicates whether to record the value of an attribute before observing an attribute change.

  • CharacterDataOldValue: Boolean value, which indicates whether the value before the change needs to be recorded when observing the characterData change.

  • AttributeFilter: An array representing a specific attribute to observe (such as [‘class’,’ SRC ‘]).

To observe which variation type, specify true in the Option object. Note that one or more of childList, Attributes, and characterData must be specified at the same time. If none is specified, an error will be reported.Copy the code

The following example looks at the new child node.

let insertedNodes = [];
const observer = new MutationObserver((mutations) = > {
  mutations.forEach((mutation) = > {
    for (var i = 0; i < mutation.addedNodes.length; i++) insertedNodes.push(mutation.addedNodes[i]); })}); observer.observe(document, { childList: true });
console.log(insertedNodes);
Copy the code
  • takeRecords()

The takeRecords method is used to clear the change record, that is, unprocessed changes are no longer processed. This method returns an array of change records.

observer.takeRecords();
Copy the code
  • disconnect()

The Disconnect method is used to stop the observation. After this method is called, the DOM changes again and the observer is not triggered.

observer.disconnect();
Copy the code

MutationRecord object

Each time the DOM changes, a change Record instance is generated. This example contains all the information related to the change. The Mutation Observer handles an array of MutationRecord instances.

The MutationRecord object contains DOM information and has the following properties:

  • Type: Indicates the observed change type (attribute, characterData, or childList).

  • Target: the DOM node that has changed.

  • AddedNodes: New DOM nodes.

  • RemovedNodes: Deletes DOM nodes.

  • PreviousSibling: previousSibling, null if none.

  • NextSibling: nextSibling, null if none.

  • AttributeName: An attribute that has changed. If attributeFilter is set, only the pre-specified attributes are returned.

  • OldValue: indicates the value before the change. This property is valid only for attribute and characterData changes, and returns NULL if a childList change occurs.

The sample application

Changes in child elements

The following example shows how to read a change record.

let callback = function (records){
  records.map((record) = > {
    console.log('Mutation type: ' + record.type);
    console.log('Mutation target: ' + record.target);
  });
};

const mo = new MutationObserver(callback);

const option = {
  'childList': true.'subtree': true
};

mo.observe(document.body, option);
Copy the code

An observer of the code above, observing changes to all children of (childList, subtree). The callback shows all changed types and target nodes on the console.

Change of attribute

The following example shows how to track property changes.

const callback = function (records) {
  records.map((record) = > {
    console.log('Previous attribute value: ' + record.oldValue);
  });
};

const mo = new MutationObserver(callback);

const element = document.getElementById('#my_element');

const options = {
  'attributes': true.'attributeOldValue': true
}

mo.observe(element, options);

Copy the code

The code above sets the trace attribute change (‘ Attributes ‘: true) and then sets the value before the record change. When a change actually occurs, the value before the change is displayed on the console.

The watermark can not be deleted

In the process of realizing the watermark cannot be deleted, there are two main logic to be dealt with:

  • Listen for changes to the watermark node
  • Listen for the deletion of the watermark node
  • Listen for changes to the watermark node
drawCanvas () { ... .// code omitted
}

canvasObserver() {
	this.drawCanvas();
	let canvasObserver = new MutationObserver((mo) = > {
        this.drawCnvas();
    });
    let config = { attributes: true.childList: true.characterData: true };
 	
	canvasObserver.observe(document.querySelector('#divContainer'), config);
}
Copy the code
  • Listen for the deletion of the watermark node
drawCanvas () { ... .// code omitted
}

canvasObserver() {
	this.drawCanvas();
	let canvasObserver = new MutationObserver((mo) = > {
        let { removedNodes } = mo[0];
        if(removedNodes.find(target)) { ... .// Find the pseudocode for the target node
        	this.drawCnvas(); }});let config = { attributes: true.childList: true.characterData: true };
 	let target = document.querySelector('#divContainer');

	canvasObserver.observe(target.parentNode, config);
}

Copy the code

Reference links:

  1. Mutation Observer API