Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 1 year ago.
Improve this question
I want to detect an element movement in my page . for example i have a bottom with offsetheight: 200px and offsetleft : 200px and i want to have a listener for detect that element position has been changed (not resized <======)
You can detect whether an attribute on an element has been changed or whether something has been added/subtracted from the DOM by using a MutationObserver.
Here's a simple example where a change anywhere in the body is noted by a console.log. The offset of the button you are interested in can then be read and checked against the original to see if it has moved.
<!doctype html>
<html>
<head>
<title>Observe</title>
<style>
.movedown {
position: relative;
width: 30vmin;
height: 30vmin;
}
.button {
width: 20vmin;
height: 20vmin;
background: pink;
}
</style>
</head>
<body>
<button onclick="this.classList.toggle('movedown');console.log('button.offsetTop = ' + button.offsetTop);">CLICK ME TO EXTEND/SHRINK ME SO THE OTHER BUTTON MOVES</button>
<button class="button">I AM THE BUTTON YOU ARE INTERESTED IN SEEING WHEN I HAVE MOVED</button>
<div></div>
<script>
//This script copied almost complete from MDN
// Select the node that will be observed for mutations
const targetNode = document.body;
const button = document.querySelector('.button');
// Options for the observer (which mutations to observe)
const config = { attributes: true, childList: true, subtree: true };
// Callback function to execute when mutations are observed
const callback = function(mutationsList, observer) {
// Use traditional 'for loops' for IE 11
for(const mutation of mutationsList) {
if (mutation.type === 'childList') {
console.log('A child node has been added or removed.');
}
else if (mutation.type === 'attributes') {
console.log('A ' + mutation.attributeName + ' attribute was modified.');
}
}
};
// Create an observer instance linked to the callback function
const observer = new MutationObserver(callback);
// Start observing the target node for configured mutations
observer.observe(targetNode, config);
</script>
</body>
</html>
Code taken from MDN where further info on the observing such mutations can be found.
Related
I'm making a simple Chrome extension that modifies some information shown on the thumbnails of the recommended videos on YouTube.
For a simplification, let's say I want to replace the video length (e.g., "14:32") with the name of the channel (e.g., "PewDiePie").
Let's say I'm in the page of any YouTube video (video player in the center, list of thumbnails on the right side).
I can do this replacement once:
function processNode(node: HTMLElement) {
const channelName = node
.closest('ytd-thumbnail')
?.parentElement?.querySelector('.ytd-channel-name')
?.querySelector('yt-formatted-string');
if (channelName?.textContent) node.textContent = channelName?.textContent;
}
async function replaceCurrentThumbnailTimes(): Promise<void> {
for (const node of document.querySelectorAll(
'span.ytd-thumbnail-overlay-time-status-renderer',
)) {
processNode(node as HTMLElement);
}
}
void replaceCurrentThumbnailTimes();
This works, but then if I navigate to a new page---for example by clicking any video in the list of recommended---the video lengths are not updated.
The values I changed remain the same, despite the thumbnails being updated to refer to a new video.
As an example, let's say I open a YouTube video and the first thumbnail on the side is a video by Alice.
The time on the thumbnail is replaced by Alice, as I wanted.
Next, I click in some other video, and the first thumbnail is now a video by Bob.
The time on that thumbnail is still Alice, despite that being out of date.
I tried using the MutationObserver API, and that works when new thumbnails are added to the DOM (e.g., when scrolling down the page), but it also doesn't work for when the existing thumbnail elements are modified.
This is what I tried:
async function replaceFutureThumbnailTimes(): Promise<void> {
const observer = new MutationObserver((mutations) => {
// For each new node added, check if it's a video thumbnail time
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (
node instanceof HTMLElement &&
node.classList.contains(
'ytd-thumbnail-overlay-time-status-renderer',
) &&
node.getAttribute('id') === 'text'
) {
processNode(node);
}
}
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
characterData: true,
attributes: true,
});
}
void replaceFutureThumbnailTimes();
I think it might have something to do with the shadow/shady DOM, but I can't figure out how to go around it.
PS: to make it simpler for others to reproduce, I put the same code in pure javascript on pastebin, so that you can just copy it into the chrome console: https://pastebin.com/NWKfzCwQ
As #RoryMcCrossan and #wOxxOm suggested in the comments to the question, indeed the MutationObserver works, and I was just misusing it. Thanks to both of them!
In this case, I needed to monitor for attributes changes, and check for changes in the aria-label, for nodes with id text.
Here is the code in javascript which accomplishes this:
async function replaceFutureThumbnailTimes() {
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
// If attributes were changed, check if it's the thumbnail time
if (
mutation.type === 'attributes' &&
mutation.attributeName === 'aria-label' &&
mutation.target.getAttribute('id') === 'text') {
processNode(mutation.target);
}
// For each new node added, check if it's a video thumbnail time
for (const node of mutation.addedNodes) {
if (
node instanceof HTMLElement &&
node.classList.contains(
'ytd-thumbnail-overlay-time-status-renderer',
) &&
node.getAttribute('id') === 'text'
) {
processNode(node);
}
}
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
characterData: false,
attributes: true,
});
}
void replaceFutureThumbnailTimes();
Is there a way to detect stylesheet changes ? MutationObserver only tracks inline css changes on the element.
html
<div class="exampleClass"></div>
js
let config = {
attributes: true,
// attributeFilter: ["style"],
};
let mutationCallback = function(mutationsList) {
mutationsList.forEach((mutation, i) => {
console.log(mutation);
});
};
let observer = new MutationObserver(mutationCallback);
observer.observe(document.querySelector('.exampleClass'), config);
If I modify the element throught js with
document.querySelector(‘exampleClass’).style.top = '10px'
or the webconsole inspector directly on the node, the mutation observer callback is called, but if the class (not the node itself) is modified in the webconsole inspector there is no callback
When I change an attribute value with the same value, looking at the inspector console, the DOM tree does not change, yet Mutation Observer triggers since I modified the attribute value, but for the actual same value.
Can someone explains how this works under the hood? I inserted a snippet to demonstrate my point.
/* OBSERVER */
var divToUpdate = document.querySelector('#update');
var config = {attributeFilter: ['data-update']};
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.target.dataset.update == 'true') {
console.log('Attribute value updated, but not really!', mutation);
}
});
});
observer.observe(divToUpdate, config);
/* BUTTON UPDATER */
document.querySelector('button').addEventListener('click', function() {
divToUpdate.setAttribute('data-update', true);
});
<div id="update" data-update="true">DIV WITH ATTRIBUTE</div>
<button type="button">UPDATE DIV ATTRIBUTE</button>
According to this history, MutationObserver was designed to work that way. Any call to setAttribute triggers a mutation, regardless of whether the value is being changed or set to the current value. https://github.com/whatwg/dom/issues/520#issuecomment-336574796
I'm trying to detect the height of an element set dynamically by an image slider plugin and use it to set the height of a container.
Getting "TypeError: MutationObserver.observe: Argument 1 does not implement interface Node."
I checked MutationObserver documentation and its options. Saw that
At a minimum, one of childList, attributes, and/or characterData must be true when you call observe(). Otherwise, a TypeError exception will be thrown.
and I am setting attributes to true but still getting the typeError
jQuery(document).ready(function($){
// Callback function to execute when mutations are observed
const callback = function(mutationsList, observer) {
for(const mutation of mutationsList) {
console.log('The ' + mutation.attributeName + ' attribute was modified.');
}
};
const observer = new MutationObserver(callback);
//set up your configuration
const config = { attributes:true, subtree: false };
var changingContainer = $('.soliloquy-viewport');
//start observing
observer.observe(changingContainer, config);
//change height on button press
function changeHeight(){
changingContainer.height(Math.floor((Math.random() * 100) + 20));
}
$("#height").click(changeHeight);
});
.soliloquy-viewport{
background: yellow;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<div class="soliloquy-viewport">
Hello
</div>
<button id="height">
change height
</button>
MutationObervers only work on an Element object, not jQuery objects. Change the first argument of the observe() to the underlying Element by using get() like this:
observer.observe(changingContainer.get(0), config);
Or by accessing the jQuery object by index like this:
observer.observe(changingContainer[0], config);
jQuery(document).ready(function($) {
let $changingContainer = $('.soliloquy-viewport');
const observer = new MutationObserver((ml, o) => {
for (const m of ml) {
console.log('The ' + m.attributeName + ' attribute was modified.');
}
});
observer.observe($changingContainer.get(0), {
attributes: true,
subtree: false
});
//change height on button press
$("#height").click(() => $changingContainer.height(Math.floor((Math.random() * 100) + 20)));
});
.soliloquy-viewport {
background: yellow;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<div class="soliloquy-viewport">Hello</div>
<button id="height">change height</button>
Note that this only works for a single element. For multiple elements with the same class, you will need to loop through them and apply the MO individually.
I need to dynamically add styling to an element based on when a descendant has a specific class. I understand that this can only be done with javascript which isn't really my department.
Although I've been looking around for some copy paste solutions I now resort to creating this thread as I feel many answers listed here may be outdated and focused on compatibility.
I am no expert on the subject but i read that this can be done quite easily for modern browsers without using jquery and I only need it to work on modern browsers.
<ul class="the-parent">
<li class="the-descendant"></li>
</ul>
What happens is that a Wordpress plugin is adding/removing class "opened" to "the-descendant" on interaction with the menu but does not provide me a way to style the parent based on this interaction.
For what I read from your question, you'd need to set up a MutationObserver on the child node, then watch for attribute changes on the class attribute:
// Get a reference to the Node you need to observe classList changes on
const targetNode = document.querySelector('.child');
// Set up the configuration for the MutationObserver
// https://developer.mozilla.org/en-US/docs/Web/API/MutationObserverInit
const config = {
attributes: true,
attributeFilter: ['class'],
};
// Callback function to execute when mutations are observed
const callback = (mutationsList, observer) => {
for (let mutation of mutationsList) {
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
mutation
.target
.closest('.parent')
.classList
.toggle('orange');
}
}
};
// Create an observer instance linked to the callback function
const observer = new MutationObserver(callback);
// Start observing the target node for configured mutations
observer.observe(targetNode, config);
// Later, you can stop observing
// observer.disconnect();
.parent {
background-color: grey;
color: white;
padding: 30px;
}
.parent.orange {
background-color: orange;
}
.child {
background-color: white;
color: black;
padding: 20px;
}
.parent::after,
.child::after {
content: '"'attr(class)'"';
}
<div class="parent">
<div class="child" onclick="this.classList.toggle('clicked');">child css class: </div>
parent css class:
</div>
Remember it's important to disconnect the observer when you no longer need it, otherwise it stays active even if the observed node is removed from the DOM.