Опубликовано: 8 октября 2024 г.
Чтобы исправить некоторые странные особенности вложенности CSS, рабочая группа CSS решила добавить интерфейс CSSNestedDeclarations
в Спецификацию вложенности CSS . Благодаря этому дополнению, помимо некоторых других улучшений, объявления, следующие за правилами стиля, больше не сдвигаются вверх.
Эти изменения доступны в Chrome начиная с версии 130 и готовы к тестированию в Firefox Nightly 132 и Safari Technology Preview 204.
Поддержка браузера
Проблема с вложенностью CSS без CSSNestedDeclarations
Одна из проблем с вложенностью CSS заключается в том, что изначально следующий фрагмент не работает так, как вы могли ожидать:
.foo {
width: fit-content;
@media screen {
background-color: red;
}
background-color: green;
}
Глядя на код, можно предположить, что элемент <div class=foo>
имеет green
background-color
потому что background-color: green;
декларация идет последней. Но в Chrome до версии 130 это не так. В тех версиях, где отсутствует поддержка CSSNestedDeclarations
, background-color
элемента — red
.
После анализа фактического правила Chrome до 130 использований выглядит следующим образом:
.foo {
width: fit-content;
background-color: green;
@media screen {
& {
background-color: red;
}
}
}
CSS после парсинга претерпел два изменения:
-
background-color: green;
был перенесен вверх, чтобы присоединиться к двум другим декларациям. - Вложенный
CSSMediaRule
был переписан, чтобы его объявления были заключены в дополнительныйCSSStyleRule
с использованием селектора&
.
Еще одно типичное изменение, которое вы здесь увидите, — это отбрасывание парсером свойств, которые он не поддерживает.
Вы можете проверить «CSS после синтаксического анализа» самостоятельно, прочитав cssText
из CSSStyleRule
.
Попробуйте сами на этой интерактивной игровой площадке :
Почему этот CSS переписан?
Чтобы понять, почему произошла эта внутренняя перезапись, вам необходимо понять, как это CSSStyleRule
представлено в объектной модели CSS (CSSOM).
В Chrome до версии 130 фрагмент CSS, опубликованный ранее, сериализуется в следующее:
↳ CSSStyleRule
.type = STYLE_RULE
.selectorText = ".foo"
.resolvedSelectorText = ".foo"
.specificity = "(0,1,0)"
.style (CSSStyleDeclaration, 2) =
- width: fit-content
- background-color: green
.cssRules (CSSRuleList, 1) =
↳ CSSMediaRule
.type = MEDIA_RULE
.cssRules (CSSRuleList, 1) =
↳ CSSStyleRule
.type = STYLE_RULE
.selectorText = "&"
.resolvedSelectorText = ":is(.foo)"
.specificity = "(0,1,0)"
.style (CSSStyleDeclaration, 1) =
- background-color: red
Из всех свойств, которыми обладает CSSStyleRule
, в данном случае актуальны следующие два:
- Свойство
style
, которое представляет собой экземплярCSSStyleDeclaration
представляющий объявления. - Свойство
cssRules
, представляющее собойCSSRuleList
, содержащее все вложенные объектыCSSRule
.
Поскольку все объявления из фрагмента CSS попадают в свойство style
CSStyleRule
, происходит потеря информации. При взгляде на свойство style
не ясно, что background-color: green
был объявлен после вложенного CSSMediaRule
.
↳ CSSStyleRule
.type = STYLE_RULE
.selectorText = ".foo"
.style (CSSStyleDeclaration, 2) =
- width: fit-content
- background-color: green
.cssRules (CSSRuleList, 1) =
↳ …
Это проблематично, поскольку для правильной работы механизма CSS он должен уметь отличать свойства, которые появляются в начале содержимого правила стиля, от тех, которые появляются вперемежку с другими правилами.
Что касается объявлений внутри CSSMediaRule
которые внезапно оказались заключены в CSSStyleRule
: это потому, что CSSMediaRule
не был разработан для содержания объявлений.
Поскольку CSSMediaRule
может содержать вложенные правила, доступные через свойство cssRules
, объявления автоматически заключаются в CSSStyleRule
.
↳ CSSMediaRule
.type = MEDIA_RULE
.cssRules (CSSRuleList, 1) =
↳ CSSStyleRule
.type = STYLE_RULE
.selectorText = "&"
.resolvedSelectorText = ":is(.foo)"
.specificity = "(0,1,0)"
.style (CSSStyleDeclaration, 1) =
- background-color: red
Как это решить?
Рабочая группа CSS рассмотрела несколько вариантов решения этой проблемы.
Одним из предложенных решений было обернуть все пустые объявления во вложенный CSSStyleRule
с помощью селектора вложенности ( &
). Эта идея была отвергнута по разным причинам, включая следующие нежелательные побочные эффекты &
обессахаривания :is(…)
:
- Это влияет на специфику. Это связано с тем, что
:is()
перенимает специфику своего наиболее конкретного аргумента. - Это не очень хорошо работает с псевдоэлементами в исходном внешнем селекторе. Это связано с тем, что
:is()
не принимает псевдоэлементы в своем аргументе списка селекторов.
Возьмем следующий пример:
#foo, .foo, .foo::before {
width: fit-content;
background-color: red;
@media screen {
background-color: green;
}
}
После анализа этого фрагмента в Chrome до 130 он становится таким:
#foo,
.foo,
.foo::before {
width: fit-content;
background-color: red;
@media screen {
& {
background-color: green;
}
}
}
Это проблема, поскольку вложенный CSSRule
с селектором &
:
- Сглаживается до
:is(#foo, .foo)
, попутно удаляя.foo::before
из списка селектора. - Имеет специфичность
(1,0,0)
, что затрудняет последующую перезапись.
Вы можете проверить это, проверив, во что сериализуется правило:
↳ CSSStyleRule
.type = STYLE_RULE
.selectorText = "#foo, .foo, .foo::before"
.resolvedSelectorText = "#foo, .foo, .foo::before"
.specificity = (1,0,0),(0,1,0),(0,1,1)
.style (CSSStyleDeclaration, 2) =
- width: fit-content
- background-color: red
.cssRules (CSSRuleList, 1) =
↳ CSSMediaRule
.type = MEDIA_RULE
.cssRules (CSSRuleList, 1) =
↳ CSSStyleRule
.type = STYLE_RULE
.selectorText = "&"
.resolvedSelectorText = ":is(#foo, .foo, .foo::before)"
.specificity = (1,0,0)
.style (CSSStyleDeclaration, 1) =
- background-color: green
Визуально это также означает, что background-color
.foo::before
red
а не green
.
Другой подход, который рассматривала рабочая группа CSS, заключался в том, чтобы заключить все вложенные объявления в правило @nest
. Это было отклонено из-за ухудшения опыта разработчиков, которое это могло бы вызвать.
Знакомство с интерфейсом CSSNestedDeclarations
Решением, на котором остановилась рабочая группа CSS, является введение правила вложенных объявлений .
Это правило вложенных объявлений реализовано в Chrome, начиная с Chrome 130.
Поддержка браузера
Введение правила вложенных объявлений изменяет синтаксический анализатор CSS для автоматического переноса последовательных напрямую вложенных объявлений в экземпляр CSSNestedDeclarations
. При сериализации этот экземпляр CSSNestedDeclarations
попадает в свойство cssRules
CSSStyleRule
.
Снова возьмем в качестве примера следующий CSSStyleRule
:
.foo {
width: fit-content;
@media screen {
background-color: red;
}
background-color: green;
}
При сериализации в Chrome 130 или новее это выглядит так:
↳ CSSStyleRule
.type = STYLE_RULE
.selectorText = ".foo"
.resolvedSelectorText = ".foo"
.specificity = (0,1,0)
.style (CSSStyleDeclaration, 1) =
- width: fit-content
.cssRules (CSSRuleList, 2) =
↳ CSSMediaRule
.type = MEDIA_RULE
.cssRules (CSSRuleList, 1) =
↳ CSSNestedDeclarations
.style (CSSStyleDeclaration, 1) =
- background-color: red
↳ CSSNestedDeclarations
.style (CSSStyleDeclaration, 1) =
- background-color: green
Поскольку правило CSSNestedDeclarations
заканчивается в CSSRuleList
, синтаксический анализатор может сохранить позицию объявления background-color: green
: после объявления background-color: red
(которое является частью CSSMediaRule
).
Более того, наличие экземпляра CSSNestedDeclarations
не приводит к каким-либо неприятным побочным эффектам, вызванным другими, теперь отброшенными потенциальными решениями: правило вложенных объявлений соответствует тем же элементам и псевдоэлементам, что и его родительское правило стиля, с той же специфичностью. поведение.
Доказательством этого является обратное чтение cssText
CSSStyleRule
. Благодаря правилу вложенных объявлений он такой же, как и входной CSS:
.foo {
width: fit-content;
@media screen {
background-color: red;
}
background-color: green;
}
Что это значит для вас
Это означает, что вложенность CSS стала намного лучше с Chrome 130. Но это также означает, что вам, возможно, придется просмотреть часть вашего кода, если вы чередуете голые объявления с вложенными правилами.
Возьмем следующий пример, в котором используется замечательный @starting-style
/* This does not work in Chrome 130 */
#mypopover:popover-open {
@starting-style {
opacity: 0;
scale: 0.5;
}
opacity: 1;
scale: 1;
}
До Chrome 130 эти декларации были подняты. В итоге вы получите opacity: 1;
и scale: 1;
объявления, входящие в CSSStyleRule.style
, за которыми следует CSSStartingStyleRule
(представляющий правило @starting-style
) в CSSStyleRule.cssRules
.
Начиная с Chrome 130, объявления больше не поднимаются, и в итоге вы получаете два вложенных объекта CSSRule
в CSSStyleRule.cssRules
. По порядку: один CSSStartingStyleRule
(представляющий правило @starting-style
) и один CSSNestedDeclarations
, содержащий opacity: 1; scale: 1;
декларации.
Из-за этого измененного поведения объявления @starting-style
перезаписываются теми, которые содержатся в экземпляре CSSNestedDeclarations
, тем самым удаляя анимацию ввода.
Чтобы исправить код, убедитесь, что блок @starting-style
идет после обычных объявлений. Вот так:
/* This works in Chrome 130 */
#mypopover:popover-open {
opacity: 1;
scale: 1;
@starting-style {
opacity: 0;
scale: 0.5;
}
}
Если при использовании вложенности CSS вы держите вложенные объявления поверх вложенных правил, ваш код в основном работает нормально со всеми версиями всех браузеров, поддерживающих вложенность CSS.
Наконец, если вы хотите определить доступность CSSNestedDeclarations
, вы можете использовать следующий фрагмент JavaScript:
if (!("CSSNestedDeclarations" in self && "style" in CSSNestedDeclarations.prototype)) {
// CSSNestedDeclarations is not available
}