@scope를 사용하여 DOM의 제한된 하위 트리 내에서만 요소를 선택하는 방법을 알아보세요.
브라우저 지원
- 118
- 118
- x
- x
CSS 선택자를 작성하는 섬세한 기술
선택기를 작성할 때는 두 세계 사이에서 갈라질 수 있습니다. 한편으로는 선택하는 요소를 매우 구체적으로 명시하는 것이 좋습니다. 반면 선택기는 쉽게 재정의할 수 있도록 유지하고 DOM 구조와 긴밀하게 결합되지 않는 것이 좋습니다.
예를 들어 '카드 구성요소의 콘텐츠 영역에 있는 히어로 이미지'(다소 구체적인 요소 선택)를 선택하려는 경우 .card > .content > img.hero
와 같은 선택기를 작성하지 않아도 됩니다.
- 이 선택기는
(0,3,1)
의 특이성이 매우 높아 코드가 커짐에 따라 재정의하기 어렵습니다. - 직접 하위 요소 연결자에 의존하여 DOM 구조와 긴밀하게 결합됩니다. 마크업이 변경되면 CSS도 변경해야 합니다.
그러나 이 요소의 선택기로 img
만 작성해서는 안 됩니다. 이렇게 하면 페이지 전체의 모든 이미지 요소가 선택되기 때문입니다.
이 과정에서 적절한 균형을 찾는 것은 매우 어려운 일입니다. 지난 몇 년 동안 일부 개발자는 이와 같은 상황에서 도움이 되는 솔루션과 해결 방법을 제시해 왔습니다. 예를 들면 다음과 같습니다.
- BEM과 같은 방법에서는 이 요소에
card__img card__img--hero
클래스를 지정하여 특이성을 낮게 유지하면서 선택한 항목을 구체적으로 지정할 수 있도록 합니다. - 범위가 지정된 CSS 또는 스타일 구성요소와 같은 자바스크립트 기반 솔루션은 무작위로 생성된 문자열(예:
sc-596d7e0e-4
)을 선택자에 추가하여 모든 선택자를 다시 씁니다. 이를 통해 선택자가 페이지의 다른 쪽에 있는 요소를 타겟팅하지 않도록 할 수 있습니다. - 일부 라이브러리에서는 선택기가 완전히 삭제되므로 스타일 지정 트리거를 마크업 자체에 직접 삽입해야 하는 라이브러리도 있습니다.
하지만 이들 중 어느 것도 필요하지 않다면 어떻게 해야 할까요? CSS가 높은 특이성의 선택기 또는 DOM에 밀접하게 결합된 선택기를 작성할 필요 없이, 어떤 요소를 선택할지를 매우 구체적으로 지정할 수 있는 방법을 제공한다면 어떨까요? 여기서 @scope
가 중요한 역할을 합니다. DOM의 하위 트리 내에서만 요소를 선택하는 방법을 제공합니다.
@scope 소개
@scope
를 사용하면 선택기의 도달범위를 제한할 수 있습니다. 타겟팅할 하위 트리의 상한선을 결정하는 범위 지정 루트를 설정하면 됩니다. 범위 지정 루트 세트를 사용하면 범위가 지정된 스타일 규칙이라는 포함된 스타일 규칙은 DOM의 제한된 하위 트리에서만 선택할 수 있습니다.
예를 들어 .card
구성요소의 <img>
요소만 타겟팅하려면 .card
를 @scope
at-rule의 범위 지정 루트로 설정합니다.
@scope (.card) {
img {
border-color: green;
}
}
범위 지정 스타일 규칙 img { … }
은 일치하는 .card
요소의 범위에 있는 요소 <img>
개만 선택할 수 있습니다.
카드의 콘텐츠 영역 (.card__content
) 내에 있는 <img>
요소가 선택되지 않도록 하려면 img
선택기를 더 구체적으로 만들면 됩니다. 또 다른 방법은 @scope
at-rule이 하한 경계를 결정하는 범위 지정 제한도 허용한다는 사실을 사용하는 것입니다.
@scope (.card) to (.card__content) {
img {
border-color: green;
}
}
이 범위가 지정된 스타일 규칙은 상위 트리에서 .card
와 .card__content
요소 사이에 있는 <img>
요소만 타겟팅합니다. 상한과 하한이 있는 이러한 유형의 범위 지정을 도넛 범위라고 합니다.
:scope
선택기
기본적으로 모든 범위 지정 스타일 규칙은 범위 지정 루트를 기준으로 합니다. 범위 지정 루트 요소 자체를 타겟팅할 수도 있습니다. 이 경우 :scope
선택기를 사용합니다.
@scope (.card) {
:scope {
/* Selects the matched .card itself */
}
img {
/* Selects img elements that are a child of .card */
}
}
범위 지정 스타일 규칙 내의 선택기에는 암시적으로 :scope
가 앞에 추가됩니다. 원하는 경우 :scope
를 직접 앞에 추가하여 명시적으로 나타낼 수 있습니다. 또는 CSS 중첩에서 &
선택기 앞에 추가할 수 있습니다.
@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 */
}
:root :root { /* ❌ 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>
위의 예에서 범위 지정 규칙은 클래스 이름이 card__header
인 div
내부의 요소만 타겟팅합니다. div
가 <style>
요소의 상위 요소이기 때문입니다.
캐스케이드의 @scope
CSS 캐스케이드 내부에 @scope
는 새로운 기준인 범위 지정 근접도도 추가합니다. 이 단계는 특수성 뒤, 그리고 나타나는 순서입니다.
사양에 따라 다음 안내를 따르세요.
범위 지정 루트가 다른 스타일 규칙에 나타나는 선언을 비교할 경우 범위 지정 루트와 범위 지정 스타일 규칙 주체 사이의 세대 또는 동위 요소 홉이 가장 적은 선언이 우선합니다.
이 새로운 단계는 구성요소의 여러 변형을 중첩할 때 유용합니다. 아직 @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>
이 약간의 마크업을 볼 때 세 번째 링크는 .light
클래스가 적용된 div
의 하위 요소라 하더라도 black
대신 white
가 됩니다. 이는 계단식 캐스케이드가 우승자를 결정하는 데 사용하는 표시 기준 순서 때문입니다. .dark a
가 마지막으로 선언된 것을 확인하므로 .light a
규칙에서 우선합니다.
이제 범위 지정 근접도 기준을 사용하면 이 문제가 해결되었습니다.
@scope (.light) {
:scope { background: #ccc; }
a { color: black;}
}
@scope (.dark) {
:scope { background: #333; }
a { color: white; }
}
범위가 지정된 a
선택기는 모두 동일한 특정성을 가지므로 범위 지정 근접도 기준이 실행됩니다. 범위 지정 루트의 근접성을 기준으로 두 선택기의 가중치를 평가합니다. 세 번째 a
요소의 경우 .light
범위 지정 루트까지만 홉이 하나뿐이고 .dark
으로 가는 홉은 2개입니다. 따라서 .light
의 a
선택기가 선택됩니다.
맺음말: 스타일 격리가 아닌 선택기 격리
한 가지 중요한 점은 @scope
는 선택기의 도달범위를 제한하며 스타일 격리를 제공하지 않는다는 것입니다. 하위 요소로 상속되는 속성은 여전히 @scope
의 하한값을 넘어 상속됩니다. 이러한 속성 중 하나가 color
입니다. 도넛 범위 내에서 이 객체를 선언할 때 color
는 여전히 도넛 구멍 안에 있는 하위 요소를 상속합니다.
@scope (.card) to (.card__content) {
:scope {
color: hotpink;
}
}
위 예에서 .card__content
요소와 그 하위 요소는 .card
에서 값을 상속받기 때문에 hotpink
색상을 사용합니다.
(커버 사진: Unsplash의 rustam burkhanov)