Reputation: 17266
I'm using a MutationObserver to look for added images to a web page. As many images are displayed via the CSS background-image
property, I'm checking the current/computed CSS style in addition to looking for img
tags, for example...
var hasBackgroundImage = function(node) {
var nodeStyle = node.currentStyle || getComputedStyle(node, null);
return (
nodeStyle &&
nodeStyle.backgroundImage &&
nodeStyle.backgroundImage != "none"
);
};
However, it seems there is a delay between a node triggering a mutation event and the CSS rules being applied so there appears to be no backgroundImage
during the mutation event even though there will be at some point later. I noticed this when the code worked during debugging (stepping with breakpoints) but didn't work at runtime. Delaying mutation event processing with setTimeout
also works but to be certain the delay needs to be quite large and changes from page to page (I'm not sure exactly when the CSS rules are guaranteed to be applied). A possible cause may simply be the delayed load or injection of CSS content.
What's the best way to achieve this functionality? I guess I'm after an on-style-change mutation, but I doubt this exists.
Upvotes: 16
Views: 25588
Reputation: 2059
I have created a package @bramus/style-observer
to allow you to do that.
Install it through NPM:
npm i @bramus/style-observer
And use it like so:
import CSSStyleObserver from '@bramus/style-observer'
const cssStyleObserver = new CSSStyleObserver(
/* Array of CSS Properties to observe */
['--variable1', '--variable2', 'display', 'border-width'],
/* This is called whenever there is a change in one of the observed properties */
(values) => {
console.log(values);
},
);
cssStyleObserver.attach(document.body);
The library does not rely on requestAnimationFrame
but instead works by declaring a very short transition on the observed properties. To cater for properties that don’t animate through interpolation – such as custom properties – it also relies on the fairly new transition-behavior: allow-discrete
. This approach is detailed in the library’s announcement post: https://brm.us/style-observer
Upvotes: 0
Reputation: 1708
I used https://github.com/keithclark/ComputedStyleObserver to observe the computed style "display" and make changes depending on its value. Here is the code: (in my case it was a Vue.js project)
setup(){
let observer = null;
const collapsableItemsOnNavbar = ref(null);
onMounted(() =>{
// This is to make sure that the Profile menu displays correctly between screen sizes
const callback = entries => {
entries.forEach(entry => {
console.log(`Property '${entry.property}' changed from '${entry.previousValue}' to '${entry.value}'`);
nextTick(() => {
let displayType = entry.value;
console.log(displayType);
if(displayType === 'none' || displayType === 'block'){
// We are in a small screen, don't display the profile picture
app.onNavbarCollapseChanged(true);
}
else{
// We are in a bigger screen, display the profile picture
app.onNavbarCollapseChanged(false);
}
});
});
}
observer = new ComputedStyleObserver(callback, ['display']);
observer.observe(collapsableItemsOnNavbar.value);
});
onBeforeUnmount(() =>{
observer.disconnect();
});
return {
collapsableItemsOnNavbar,
};
},
Upvotes: 0
Reputation: 17266
This works for me...
Use a mutation observer to catch changes to the style attribute...
var observer = new MutationObserver(parseMutations);
observer.observe(document, {
...
attributes: true,
attributeFilter: ["style"]
});
...
if (mutation.attributeName) //we'll assume it's "style"
parseNode(mutation.target); //check for style.backgroundImage and call filterNode()
This works for both setAttribute("style", ...)
and element.style.whatever = something
.
Catch new style
and link
elements with the mutation observer, add an onload
event and parse applicable nodes...
var stylenodes = ["STYLE", "LINK"];
...
for (var i = 0; i < mutation.addedNodes.length; i++)
{
var node = mutation.addedNodes[i];
var nodeName = node.nodeName.toUpperCase();
if (stylenodes.indexOf(nodeName) !== -1)
node.addEventListener("load", styleLoaded);
...
//catch loading of stylenodes and parse all new rules
var currentLoadedStyles = [];
var styleLoaded = function() {
//check all styles and look for one that has just added some rules
for (var i = 0; i < document.styleSheets.length; ++i)
{
if (document.styleSheets[i].rules && document.styleSheets[i].rules.length > 0 && currentLoadedStyles.indexOf(document.styleSheets[i]) == -1)
{
currentLoadedStyles.push(document.styleSheets[i]);
parseNewStyle(document.styleSheets[i].rules);
}
}
};
//look for rules with background images and re-filter all nodes it applies to
var parseNewStyle = function(rules) {
for (var i = 0; i < rules.length; ++i)
{
//if any rule contains a background-image (could look for anything here really)
if (rules[i].style && rules[i].style.backgroundImage && rules[i].style.backgroundImage != "none")
{
//get all affected nodes and re-parse them
var nodes = document.querySelectorAll(rules[i].selectorText);
for (var j = 0; j < nodes.length; ++j)
filterNode(nodes[j]);
}
}
};
Upvotes: 19