Я использую 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>
Проблема связана со спецификой. Новое правило --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
), вставленное просто вставляется в лист перед исходным.
Верно, хотя приоритет также учитывается при расчетах специфичности.
Вы правильно вставляете правило в 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>
Кстати, я не уверен, что можно использовать
replace
илиreplaceSync
с переменными; единственный раз, когда я видел его использование, в том числе в документации, он указываетtext
, что означает буквальное правило CSS, такое какbackground: blue
, а не переменную с этим значением, хранящимся внутри.