Почему обновление существующего 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>

Кстати, я не уверен, что можно использовать replace или replaceSync с переменными; единственный раз, когда я видел его использование, в том числе в документации, он указывает text, что означает буквальное правило CSS, такое как background: blue, а не переменную с этим значением, хранящимся внутри.

TylerH 08.08.2024 16:38
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
2
1
50
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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>

Я бы сказал, что это скорее вопрос каскадирования, а не специфики. Правила имеют одинаковую специфику (они оба просто применяются к :root), вставленное просто вставляется в лист перед исходным.

TylerH 08.08.2024 16:17

Верно, хотя приоритет также учитывается при расчетах специфичности.

Rory McCrossan 08.08.2024 16:18

Вы правильно вставляете правило в 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>

Другие вопросы по теме