Почему обновление существующего CSSStyleSheet новым правилом не дает никакого эффекта?

Я использую new CSSStyleSheet() для создания пользовательской таблицы стилей для конкретного документа, стили которой зависят от содержимого.
В эту таблицу стилей я добавляю переменные CSS с помощью корневого селектора. Затем элементы в проекте будут выбираться по идентификатору и получать определенные правила, использующие эти переменные. Проблема в том, что я раньше не знал идентификаторы, поэтому стили и селекторы должны создаваться динамически.
До сих пор это работает безупречно.

Проблема, с которой я сейчас столкнулся, заключается в том, что во время некоторых определенных событий значение переменных CSS должно меняться. Я попробовал изменить его, используя insertRule() с индексом и без него. Еще я пробовала replace() и replaceSync(). Ничего не дало желаемого результата.

Теперь вопрос в том, как изменить root в этой таблице стилей?

const CSS = new CSSStyleSheet();

const rootRule = `:root{
  --background: red;
}`;
CSS.insertRule(rootRule, 0);

const bodyRule = `body { 
  background: var(--background); 
}`
CSS.insertRule(bodyRule, 1);

document.adoptedStyleSheets = [CSS];


// change color
BUTTON.addEventListener('click', function() {
  console.info('button clicked');
  const newRule = `:root {
    --background: blue;
  }`
  CSS.insertRule(newRule, 0);
})
<button id = "BUTTON">Change background-color</button>

🤔 А знаете ли вы, что...
JavaScript позволяет создавать собственные серверные приложения с использованием платформы Node.js.


2
50
2

Ответы:

Решено

Проблема связана со спецификой. Новое правило --background: blue применяется, но оно переопределяется существующим правилом:

Быстрым и грязным решением этой проблемы было бы использование !important, хотя это не идеально:

const newRule = `:root {
  --background: blue !important;
}

Лучшим подходом было бы удалить исходное правило перед добавлением нового. Это можно сделать с помощью deleteRule(). Вот рабочий пример:

const CSS = new CSSStyleSheet();

const rootRule = `:root{
  --background: red;
}`;
CSS.insertRule(rootRule, 0);

const bodyRule = `body { 
  background: var(--background); 
}`
CSS.insertRule(bodyRule, 1);
document.adoptedStyleSheets = [CSS];


BUTTON.addEventListener('click', function() {
  CSS.deleteRule(0); // delete the original :root rule
  
  const newRule = `:root {
    --background: blue;
  }`
  CSS.insertRule(newRule, 0);
})
<button id = "BUTTON">Change background-color</button>

Вы правильно вставляете правило в CSSStyleSheet (это можно узнать, добавив console.info(CSS.cssRules[0].cssText); к нажатию кнопки, чтобы проверить, вставлено ли оно). Проблема, с которой вы столкнулись, заключается в том, что ваш код вставляется по индексу 0, то есть перед существующим rootRule. Это положение по умолчанию, но вы также можете указать его с помощью CSS.insertRule(newRule, 0);. А поскольку правила для конкретных селекторов в CSS перезаписываются более поздними правилами тех же селекторов, rootRule по-прежнему применяется.

Измените его на CSS.insertRule(newRule, 2); и вы увидите, что это работает:

const CSS = new CSSStyleSheet();

const rootRule = `:root{
  --background: red;
}`;
CSS.insertRule(rootRule, 0);

const bodyRule = `body { 
  background: var(--background); 
}`
CSS.insertRule(bodyRule, 1);

document.adoptedStyleSheets = [CSS];


// change color
BUTTON.addEventListener('click', function() {
  console.info('button clicked');
  const newRule = `:root {
    --background: blue;
  }`
  CSS.insertRule(newRule, 2);
})
<button id = "BUTTON">Change background-color</button>