Ограничьте охват селекторов с помощью правила CSS @scope. Ограничьте охват селекторов с помощью правила CSS @scope.

Узнайте, как использовать @scope для выбора элементов только в пределах ограниченного поддерева вашего DOM.

Browser Support

  • Хром: 118.
  • Край: 118.
  • Firefox: за флагом.
  • Сафари: 17.4.

Source

Тонкое искусство написания селекторов CSS

При написании селекторов вы можете оказаться разрывающимся между двумя мирами. С одной стороны, вам нужно быть очень конкретным в отношении того, какие элементы вы выбираете. С другой стороны, вы хотите, чтобы ваши селекторы легко переопределялись и не были тесно связаны со структурой DOM.

Например, если вы хотите выбрать «главное изображение в области содержимого компонента карты» — что является довольно специфическим выбором элемента — вы, скорее всего, не захотите писать селектор типа .card > .content > img.hero .

  • Этот селектор имеет довольно высокую специфичность (0,3,1) , что затрудняет его переопределение по мере роста вашего кода.
  • Полагаясь на прямой дочерний комбинатор, он тесно связан со структурой DOM. Если разметка когда-либо изменится, вам также необходимо изменить CSS.

Но вы также не хотите писать просто img в качестве селектора для этого элемента, поскольку это приведет к выбору всех элементов изображения на вашей странице.

Найти правильный баланс в этом часто бывает довольно непросто. За прошедшие годы некоторые разработчики придумали решения и обходные пути, которые помогут вам в подобных ситуациях. Например:

  • Такие методологии, как БЭМ, требуют, чтобы вы присваивали этому элементу класс card__img card__img--hero , чтобы сохранить низкую специфичность, в то же время позволяя вам быть конкретными в том, что вы выбираете.
  • Решения на основе JavaScript, такие как Scoped CSS или Styled Components, переписывают все ваши селекторы, добавляя случайно сгенерированные строки, такие как sc-596d7e0e-4 , в ваши селекторы, чтобы предотвратить их нацеливание на элементы на другой стороне вашей страницы.
  • Некоторые библиотеки даже вообще отменяют селекторы и требуют размещать триггеры стилей непосредственно в самой разметке.

Но что, если вам ничего из этого не нужно? Что, если бы CSS дал вам возможность быть достаточно конкретными в отношении того, какие элементы вы выбираете, не требуя при этом писать селекторы с высокой специфичностью или те, которые тесно связаны с вашим DOM? Что ж, именно здесь в игру вступает @scope , предлагающий вам возможность выбирать элементы только внутри поддерева вашего DOM.

Представляем @scope

С помощью @scope вы можете ограничить охват ваших селекторов. Вы делаете это, устанавливая корень области видимости , который определяет верхнюю границу поддерева, на которое вы хотите ориентироваться. При наличии корневого набора области действия содержащиеся в нем правила стиля (называемые правилами стиля с ограниченной областью действия ) могут выбирать только из этого ограниченного поддерева DOM.

Например, чтобы настроить таргетинг только на элементы <img> в компоненте .card , вы устанавливаете .card в качестве корня области действия at-правила @scope .

@scope (.card) {     img {         border-color: green;     } } 

Правило ограниченного стиля img { … } может эффективно выбирать только те элементы <img> , которые находятся в области действия соответствующего элемента .card .

Чтобы предотвратить выбор элементов <img> внутри области содержимого карточки ( .card__content ), вы можете сделать селектор img более конкретным. Другой способ сделать это — использовать тот факт, что at-правило @scope также принимает предел области видимости , который определяет нижнюю границу.

@scope (.card) to (.card__content) {     img {         border-color: green;     } } 

Это правило стиля с ограниченной областью действия предназначено только для элементов <img> , которые расположены между элементами .card и .card__content в дереве предков. Этот тип области видимости — с верхней и нижней границей — часто называют кольцевой областью действия.

Селектор :scope

По умолчанию все правила стиля с ограниченной областью действия относятся к корневой области области действия. Также возможно настроить таргетинг на сам корневой элемент области видимости. Для этого используйте селектор :scope .

@scope (.card) {     :scope {         /* Selects the matched .card itself */     }     img {        /* Selects img elements that are a child of .card */     } } 

Селекторы внутри правил стиля с ограниченной областью действия неявно добавляют к началу :scope . Если хотите, вы можете указать это явно, добавив :scope самостоятельно. В качестве альтернативы вы можете добавить селектор & из CSS Nesting .

@scope (.card) {     img {        /* Selects img elements that are a child of .card */     }     :scope img {         /* Also selects img elements that are a child of .card */     }     & img {         /* Also selects img elements that are a child of .card */     } } 

Ограничение области видимости может использовать псевдокласс :scope чтобы требовать определенного отношения к корню области видимости:

/* .content is only a limit when it is a direct child of the :scope */ @scope (.media-object) to (:scope > .content) { ... } 

Ограничение области действия также может ссылаться на элементы за пределами их корня области действия, используя :scope . Например:

/* .content is only a limit when the :scope is inside .sidebar */ @scope (.media-object) to (.sidebar :scope .content) { ... } 

Обратите внимание, что сами правила стиля с ограниченной областью действия не могут выйти за пределы поддерева. Такие выборы, как :scope + p , недействительны, поскольку при этом пытаются выбрать элементы, не входящие в область видимости.

@scope и специфика

Селекторы, которые вы используете в прелюдии к @scope , не влияют на специфичность содержащихся в них селекторов. В приведенном ниже примере специфичность селектора img по-прежнему равна (0,0,1) .

@scope (#sidebar) {     img { /* Specificity = (0,0,1) */              } } 

Специфика :scope заключается в том, что он является обычным псевдоклассом, а именно (0,1,0) .

@scope (#sidebar) {     :scope img { /* Specificity = (0,1,0) + (0,0,1) = (0,1,1) */              } } 

В следующем примере внутренне & перезаписывается на селектор, который используется для корня области видимости, завернутый в селектор :is() . В конце концов, браузер будет использовать :is(#sidebar, .card) img в качестве селектора для сопоставления. Этот процесс известен как обессахаривание .

@scope (#sidebar, .card) {     & img { /* desugars to `:is(#sidebar, .card) img` */              } } 

Поскольку & очищается с помощью :is() , специфичность & рассчитывается в соответствии с правилами специфичности :is() : специфичность & равна специфичности его наиболее конкретного аргумента.

Применительно к этому примеру, особенностью :is(#sidebar, .card) является специфика его самого конкретного аргумента, а именно #sidebar , и поэтому он становится (1,0,0) . Объедините это со спецификой img (0,0,1) — и вы получите (1,0,1) как специфику для всего сложного селектора.

@scope (#sidebar, .card) {     & img { /* Specificity = (1,0,0) + (0,0,1) = (1,0,1) */              } } 

Разница между :scope и & внутри @scope

Помимо различий в том, как рассчитывается специфичность, еще одно различие между :scope и & заключается в том, что :scope представляет совпадающий корень области видимости, тогда как & представляет селектор, используемый для сопоставления корня области видимости.

По этой причине & можно использовать несколько раз. В этом отличие от :scope , который вы можете использовать только один раз, поскольку вы не можете сопоставить корень области видимости внутри корня области видимости.

@scope (.card) {   & & { /* Selects a `.card` in the matched root .card */   }   :scope :scope { /* ❌ Does not work */        } } 

Объем без прелюдий

При написании встроенных стилей с помощью элемента <style> вы можете ограничить правила стиля родительским элементом, включающим элемент <style> , не указывая какой-либо корень области видимости. Вы делаете это, опуская прелюдию @scope .

<div class="card">   <div class="card__header">     <style>       @scope {         img {           border-color: green;         }       }     </style>     <h1>Card Title</h1>     <img src="…" height="32" class="hero">   </div>   <div class="card__content">     <p><img src="…" height="32"></p>   </div> </div> 

В приведенном выше примере правила с областью действия нацелены только на элементы внутри div с именем класса card__header , поскольку этот div является родительским элементом элемента <style> .

@scope в каскаде

Внутри CSS Cascade @scope также добавляет новый критерий: область видимости близости . Этот шаг следует за конкретикой, но перед порядком появления.

Визуализация каскада CSS.

Согласно спецификации :

При сравнении объявлений, которые появляются в правилах стиля с разными корнями области видимости, побеждает объявление с наименьшим количеством переходов между поколениями или родственными элементами между корнем области видимости и субъектом правила стиля с областью действия.

Этот новый шаг пригодится при вложении нескольких вариантов компонента. Возьмем этот пример, который пока не использует @scope :

<style>     .light { background: #ccc; }     .dark  { background: #333; }     .light a { color: black; }     .dark a { color: white; } </style> <div class="light">     <p><a href="#">What color am I?</a></p>     <div class="dark">         <p><a href="#">What about me?</a></p>         <div class="light">             <p><a href="#">Am I the same as the first?</a></p>         </div>     </div> </div> 

При просмотре этого небольшого фрагмента разметки третья ссылка будет white , а не black , даже если она является дочерним элементом элемента div с примененным к нему классом .light . Это связано с критерием порядка появления, который каскад использует здесь для определения победителя. Он видит, что .dark a был объявлен последним, поэтому он выиграет от правила .light a

Теперь эта проблема решена с помощью критерия близости области действия:

@scope (.light) {     :scope { background: #ccc; }     a { color: black;} }  @scope (.dark) {     :scope { background: #333; }     a { color: white; } } 

Поскольку оба a с областью действия имеют одинаковую специфику, в действие вступает критерий близости области действия. Он взвешивает оба селектора по близости к их корню области видимости. Для этого третьего a требуется только один переход к корневому элементу области видимости .light и два к корневому элементу .dark . Поэтому победит a в .light .

Заключительное примечание: изоляция селектора, а не изоляция стиля.

Важно отметить, что @scope ограничивает возможности селекторов и не обеспечивает изоляцию стилей. Свойства, которые наследуются до дочерних элементов, по-прежнему будут наследовать за пределами нижней границы @scope . Одним из таких свойств является color . При объявлении того, что находится внутри области пончика, color по-прежнему будет наследоваться до детей внутри отверстия пончика.

@scope (.card) to (.card__content) {   :scope {     color: hotpink;   } } 

В приведенном выше примере элемент .card__content и его дочерние элементы имеют hotpink цвет, поскольку они наследуют значение от .card .

(Фото на обложке Рустама Бурханова на Unsplash )