Опубликовано: 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 }