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