javascriptmutation-observers

Observing Constructible StyleSheets changes


I have the following code snippet which uses Constructible Stylesheets. (when clicking on the button the attached constructible stylesheet is updated with a random color).

How do I observe and listen to this changes? (I would like to capture the computed style changes on the element, or alternatively - the updated constructible stylesheet)

var div1 = document.getElementById("div1");


var observer = new MutationObserver(function (mutations) {
  let text = "";
  mutations.forEach(function (mutation) {
    text += "changed " + mutation.attributeName;
  });
  document.getElementById("output").innerHTML += `<div>${text}</div>`;
});

var observerConfig = {
  attributes: true
};

var targetNode = document.getElementById("div1");

document.getElementById("btnAttachStyleSheet").addEventListener("click", () => {
  switch (Math.floor(Math.random() * 5)) {
    case 1:
      sheet.replaceSync(".color {color:blue}");
      break;
    case 2:
      sheet.replaceSync(".color {color:yellow}");
      break;
    case 3:
      sheet.replaceSync(".color {color:green}");
      break;
    case 4:
      sheet.replaceSync(".color {color:brown}");
      break;
    default:
      sheet.replaceSync(".color {color:pink}");
  }
});
const sheet = new CSSStyleSheet();
document.adoptedStyleSheets = [sheet];
observer.observe(targetNode, observerConfig);
<h1>Adopted stylesheets</h1>
<button id="btnAttachStyleSheet">Update style sheet</button>
<div style="padding: 1rem;" id="div1" class="color">colored div</div>

<h3>mutations:</h3>
<div id="output"></div>


Solution

  • The specification for replaceSync doesn't mention triggering any events. It's not a DOM mutation, so you can't watch for it with a MutationObserver.

    Instead, if you control the code calling replaceSync, call your own function that does replaceSync and then raises an event specific to your code (and similarly for replace):

    var div1 = document.getElementById("div1");
    
    var targetNode = document.getElementById("div1");
    
    document.getElementById("btnAttachStyleSheet").addEventListener("click", () => {
      switch (Math.floor(Math.random() * 5)) {
        case 1:
          sheetReplaceSync(sheet, ".color {color:blue}");
          break;
        case 2:
          sheetReplaceSync(sheet, ".color {color:yellow}");
          break;
        case 3:
          sheetReplaceSync(sheet, ".color {color:green}");
          break;
        case 4:
          sheetReplaceSync(sheet, ".color {color:brown}");
          break;
        default:
          sheetReplaceSync(sheet, ".color {color:pink}");
      }
    });
    const sheet = new CSSStyleSheet();
    function sheetReplaceSync(sheet, ...args) {
        const result = sheet.replaceSync(...args);
        console.log("Changed!");
        return result;
    }
    function sheetReplace(sheet, ...args) {
        return sheet.replace(...args).then(result => {
            console.log("Changed!");
            return result;
        });
    };
    document.adoptedStyleSheets = [sheet];
    <h1>Adopted stylesheets</h1>
    <button id="btnAttachStyleSheet">Update style sheet</button>
    <div style="padding: 1rem;" id="div1" class="color">colored div</div>
    <h3>mutations:</h3>
    <div id="output"></div>

    If you don't control the code calling replaceSync, you could replace the method (and replace) on the sheet to tap into it, but I'd avoid that if possible:

    var div1 = document.getElementById("div1");
    
    var targetNode = document.getElementById("div1");
    
    document.getElementById("btnAttachStyleSheet").addEventListener("click", () => {
      switch (Math.floor(Math.random() * 5)) {
        case 1:
          sheet.replaceSync(".color {color:blue}");
          break;
        case 2:
          sheet.replaceSync(".color {color:yellow}");
          break;
        case 3:
          sheet.replaceSync(".color {color:green}");
          break;
        case 4:
          sheet.replaceSync(".color {color:brown}");
          break;
        default:
          sheet.replaceSync(".color {color:pink}");
      }
    });
    const sheet = new CSSStyleSheet();
    const replaceSync = sheet.replaceSync;
    const replace = sheet.replace;
    sheet.replaceSync = function(...args) {
        const result = replaceSync.call(this, ...args);
        console.log("Changed!");
        return result;
    };
    sheet.replace = function(...args) {
        return replace.call(this, ...args).then(result => {
            console.log("Changed!");
            return result;
        });
    };
    document.adoptedStyleSheets = [sheet];
    <h1>Adopted stylesheets</h1>
    <button id="btnAttachStyleSheet">Update style sheet</button>
    <div style="padding: 1rem;" id="div1" class="color">colored div</div>
    <h3>mutations:</h3>
    <div id="output"></div>