1

Given some text:

<div>text</div>

I would like to detect when the computed CSS property color changes for this div.


There could be a number of css queries that would change its color, like media queries:

div {
  color: black;

  @media (prefers-color-scheme: dark) {
    color: white;
  }

  @media screen and (max-width: 992px) {
    background-color: blue;
  }
}

Or perhaps a class applied to a parent:

div {
}

body.black {
  color: white;
}

How can I, using Javascript, observe this change of computed style?

Amit
  • 5,924
  • 7
  • 46
  • 94
  • 1
    maybe something like this [Mutation Observer](https://stackoverflow.com/questions/20364687/mutationobserver-and-current-computed-css-styles) – Juan Eizmendi Jul 24 '21 at 07:06
  • 1
    That seems to work only if the style `attribute` is modified specifically, not if the computed style changes – Amit Jul 24 '21 at 07:14

2 Answers2

1

I think we can get part way there at least by using mutation observer on the whole html, that will detect things like change to the attributes of anything, which may or may not influence the color on our to be 'observed' element, and by listening for a resize event which will at least catch common media query changes. This snippet just alerts the color but of course in practice you'll want to remember the previous color (or whatever you are interested in) and check to see if it is different rather than alerting it.

const observed = document.querySelector('.observed');
const html = document.querySelector("html");
let style = window.getComputedStyle(observed);
// NOTE: from MDN: The returned style is a live CSSStyleDeclaration object, which updates automatically when the element's styles are changed.
const observer = new MutationObserver(function(mutations) {
  alert('a mutation observed');
  mutations.forEach(function(mutation) {
    alert(style.color);
  });
});

function look() {
  alert(style.color);
}
observer.observe(html, {
  attributes: true,
  subtree: true,
  childList: true
});
window.onresize = look;
  .observed {
  width: 50vmin;
  height: 50vmin;
  color: red;
}

@media (min-width: 1024px) {
  .observed {
    color: blue;
  }
}

@media (max-width: 700px) {
  .observed {
    color: gold;
  }
<div class="observed">See this color changing.<br> Either click a button or resize the viewport.</div>

<button onclick="observed.style.color = 'purple';">Click for purple</button>
<button onclick="observed.style.color = 'magenta';">Click for magenta</button>
<button onclick="observed.style.color = 'cyan';">Click for cyan</button>

What I don't know is how many other things might influence the setting - I see no way of finding out when the thing is print rather than screen for example. Hopefully someone will be able to fill in any gaps.

A Haworth
  • 30,908
  • 4
  • 11
  • 14
  • I am using angular, I think this will trigger on every change detection of any component, anywhere, which seems "expensive" to me. I'm ideally looking for a solution to actually listen to the computed style somehow – Amit Jul 24 '21 at 13:18
  • 1
    Yes, I haven't found one I'm afraid. If you have a lot of changes going on,i.e. not just things the user does, then you'd have to know what events will change what you want to track I think. Depends on whether it's all your own code or not I guess. – A Haworth Jul 24 '21 at 14:48
0

I wrote a small program that detects change in a certain CSS property, in getComputedStyle.
Note: Using too many observeChange() may cause performance issues.

let div = document.querySelector("div")

function observeChange(elem, prop, callback) {
  var styles = convertObject(getComputedStyle(elem));
  setInterval(() => {
      let newStyle = convertObject(getComputedStyle(elem));
      if (styles[prop] !== newStyle[prop]) {  //Check if styles are different or not
        callback(prop, styles[prop], newStyle[prop]);
        styles = newStyle;    //Set new styles to previous object
      }
    },
    500);  //You can change the delay
}

//Callback function
function callback(prop, old, newVal) {
  console.log(prop + " changed from " + old + " to " + newVal);
}

observeChange(div, "color", callback)

//Convert CSS2Properties object to a normal object
function convertObject(obj) {
  let object = {}
  for (let i of obj) {
    object[i] = obj[i]
  }
  return object
}
div {
  color: green;
}

input:checked+div {
  color: red;
}
<input type="checkbox">
<div>Hello World</div>
TechySharnav
  • 4,869
  • 2
  • 11
  • 29
  • Thanks. Using an interval is not something I would like to do, I would like to actually observe the browser's internal change if it exists. – Amit Jul 24 '21 at 11:48
  • @Amit Initially, I was also reluctant to use `setInterval`, but there is no other way I could think of. – TechySharnav Jul 24 '21 at 11:58