:where() 가상 선택자
`:where` 의사 클래스 선택자는 css 코딩할 때 선택자의 중복을 줄이는 데 도움이 된다.
예전에는 다음과 같이 여러 엘리먼트 안에 있는 anchor 태그에 hover 효과를 주기위해선 각 선택자들을 콤마(`,` = '또는' 논리)를 이어 나열하여 표현하여야 했었다.
<nav>
<ul>
<a href="#">element 1</a>
</ul>
</nav>
<footer>
<ol>
<a href="#">element 2</a>
</ol>
</footer>
<aside>
<p>
<a href="#">element 3</a>
</p>
</aside>
nav > ul a:hover,
footer > ol a:hover,
aside > p a:hover {
color: purple;
text-decoration: underline wavy deeppink;
}
그런데 코드를 보면 `a:hover` 부분이 각 선택자마다 중복되는 것을 확인 할 수 있을 것이다. 거기다 적용해야할 요소가 많으면 많을 수록 선택자의 양도 늘어나 나중에 가독성 및 유지보수도 힘들어지게 되며, 만일 쉼표로 이어진 이들 선택자중 하나가 잘못되면 전체가 적용되어지지 않는 문제점이 생기게 된다.
:where() 사용법
불필요한 중복을 없애 코드 자체도 짧아지게 되고, 각 선택자를 개별적으로 분석하기 때문에 만일 잘못된 선택자가 있어도 이해가 안되면 그대로 버려버려 확장성도 좋다.
:where(nav > ul, footer > ol, aside > p) a:hover {
color: purple;
text-decoration: underline wavy deeppink;
}
section h1, section h2, section h3, section h4, section h5, section h6,
article h1, article h2, article h3, article h4, article h5, article h6,
aside h1, aside h2, aside h3, aside h4, aside h5, aside h6,
nav h1, nav h2, nav h3, nav h4, nav h5, nav h6 {
color: #BADA55;
}
/* ↓ */
:where(section, article, aside, nav) :where(h1, h2, h3, h4, h5, h6) {
color: #BADA55;
}
:where() 한계점
다만 `::before` 나 `::after` 와 같은 가상 요소는 DOM에 있는 요소가 아니므로 선택이 불가능해 묶을수 없다는 한계가 있다.
/* 동작하지 않음 */
a:where(::before, ::after) {
...
}
/* 어쩔수 없이 쉼표로 이어야 한다 */
a::before,
a::after {
...
}
is() 가상 선택자
이 선택자는 위에서 배운 `:where` 선택자와 동일하게 동작된다.
단, 유일한 차이점은 명시도에 있다.
:where()와 :is()의 차이점
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Studying css selector level4</title>
<style>
div {
width: 100px;
height: 100px;
margin: 10px;
}
section {
display: flex;
}
/* 태그 선택자(*) ---> 명시도 1점 */
div {
background-color: red;
}
/* 위에보다 선언순서가 더 뒤임에도 불구하고 green이 적용되지 않았다.
✅ 명시도 1점보다 더 낮은 명시도 0 */
:where(#div1, #div2, #div3) {
background-color: green;
}
/* 반면 :is는 명시도 0점보다 높은 명시도를 가진다. */
:is(#is-div1, #is-div2, #is-div3) {
background-color: green;
}
</style>
</head>
<body>
<section class="where-village">
<div id="div1" class="div1"></div>
<div id="div2" class="div2"></div>
<div id="div3" class="div3"></div>
</section>
<section class="is-village">
<div id="is-div1" class="is-div1"></div>
<div id="is-div2" class="is-div2"></div>
<div id="is-div3" class="is-div3"></div>
</section>
</body>
</html>

`:where()`은 무슨 짓을 하든 명시도가 0이다. `red` 배경색을 주는 선언(태그 선택자 명시도 1점)보다 선언순서가 더 뒤에 있음에도 불구하고 `green`색상이 적용되지 않았다. 심지어 안에 `id`선택자를 줘도 명시도가 높게 책정되지 않았다. 그러나 `:is()`는 명시도가 높아 위 코드에서 가장 높은 우선순위를 가지기 때문에 해당 스타일로 적용되게 된다.
`:is` 가상 클래스의 명시도는 셀렉터 안에 오는 인자들 중 가장 명시도가 큰 것으로 명시도가 정해진다.
이 말이 무슨 뜻이냐면 () 안에 들어온 인자들 중 가장 명시도가 큰 것으로 `:is()`구문 전체의 명시도가 정해진다는 뜻이다.
여러 개의 인자가 들어왔을 때 명시도가 낮은 게 섞여 있더라도 그 중 명시도가 가장 높은 인자의 명시도를 나머지 인자들이 따라간다.
예를 보도록 하자. 첫 번째 예시에서는 `:is` 안의 `div#absolute`가 있기에 그보다 위에서 선언한 div#perfect와 동일한 명시도를 갖는다. 하지만 선언순서가 더 뒤에 있기에 후술자(선언된 순서가 더 뒤)로서 color:red;가 적용된다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
div#perfect span {
color: blue;
}
:is(div, div#absolute, div) span {
color: red;
}
</style>
</head>
<body>
<div><span>Document</span></div>
<div id="absolute" class="docu2"><span>Document</span></div>
<div id="perfect"><span>Document</span></div>
</body>
</html>

파랑색 글씨를 주는 셀렉터를 밑으로 보내면 명시도는 같지만 선언순서가 바뀌었다. 그래서 후술된 `div#perfect span`이 이겨서 마지막 텍스트는 blue 색상이 된다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
:is(div, div#absolute, div) span {
color: red;
}
div#perfect span {
color: blue;
} /* 0-0-2 */
</style>
</head>
<body>
<div><span>Document</span></div>
<div id="absolute" class="docu2"><span>Document</span></div>
<div id="perfect"><span>Document</span></div>
</body>
</html>

:is() 한계점
하지만 `:is` 가상 클래스도 가상 요소를 매칭하지는 않는다는 단점이 있다.
아래 코드는 매칭이 안된다.
some-element:is(::before, ::after) {
display: block;
}
:is(some-element::before, some-element::after) {
display: block;
}
`,`를 사용하여 아래처럼 수정해야 한다.
some-element::before,
some-element::after {
display: block;
}
:where() 와 :is() 중에서 어떤 것을 써야 할까?
웬만하면 :where()을 쓰는 것을 추천한다. 왜냐하면 :is()를 사용하고 밑에서 계속해서 명시도를 일일이 기억하며 관리하기는 까다로운 일이기 때문에 명시도가 0인 `:where`을 사용하여 전체적으로 빠르게 기본 스타일을 적용하고 나중에 덮어쓰는 것이 좋기 때문이다.(기존 선언에 명시도를 고려하지 않고도 쉽게 override가 가능해서 확장성이 더 좋다는 뜻)
:has()
`:has()`는 최근에 추가된 선택자이다. 이 선택자는 해당 부모 선택자가 특정 선택자를 가지고 있을 경우 적용하는 이른바 css의 if문 버전이라 보면 된다. 특정한 선택자를 가지고 있어야 하는 조건을 충족하면, `:`을 기준으로 (좌항)자기자신을 선택하게 된다.
사용법은 예를 들어 `div.parent`가 만일 p 요소를 가지고 있는 경우에만 자신의 요소에 스타일을 적용하고 싶다면 다음과 같다.
<div class="parent">
<p>Child</p>
</div>
/* div 요소가 자식으로 p를 가지고 있을 경우 스타일 적용 */
div:has(> p) {
background: red;
}

`:has()` 괄호 안 매개변수로 선택자 문법을 넣음으로써 자식, 형제, 자손 선택자를 다양한 폭으로 지정해줄 수 있다.
/* a 요소가 자손으로 img를 가지고 있을 경우 */
a:has(img) {
}
/* a 요소가 자식으로 img를 가지고 있을 경우 */
a:has(> img) {
}
/* h3 요소가 인접 형제로 p(바로 뒤따르는 p)를 가지고 있을 경우 */
h3:has(+ p) {
}
/* article 요소가 자손으로 h3 또는 p를 가지고 있을 경우 (또는 논리) */
article:has(h3, p) {
}
`:not()`과 조합한 예시이다.
/* div 요소가 자손으로 h3을 가지고 있지 않을 경우 전체 칼라 적용 */
div.section1:not(:has(h3)) {
color: red;
}
/* div 요소의 자손이 h3이 아닌 요소가 있을 경우 전체 칼라 적용 */
div.section2:has(:not(h3)) {
color: blue;
}
자바스크립트 셀렉터 조합
`:has()` 선택자는 스크립트로 동적으로 요소를 조작해야 할때 빛을 발한다.
예를들어 `div.parent` 가 만일 p 요소를 가지고 있는 경우에만 엘리먼트를 가져오고 싶다면, 기존에는 child 요소를 일일히 순회해서 탐색하며 분간해야 됬지만, 이 selector 하나로 곧바로 일치하는 요소를 가져올 수 있게 되었다.
<div class="parent">
<input type="checkbox" />
<p>Child</p>
</div>
<div class="parent">
<input type="radio" />
<p>Child</p>
</div>
<div class="parent">
<p>Child</p>
</div>
document.querySelectorAll('.parent:has(p)')
/* 제이쿼리도 자체 지원한다. */
$('.parent:has(p)')
:where, :is와 :has의 뚜렷한 차이점
`:has`는 자기자신이 가진 attribute는 셀렉터 조건에 포함하지 않는다. 하지만, `:where`과 `:is`는 자기 자신이 가진 attribute도 셀렉터 조건에 포함시킨다. 아래 예시를 보면 id를 포함한 `:has`는 색깔이 변하지 않지만, id를 포함한 `:where`는 선택이 되어서 red 색으로 글씨 색깔이 변하는 것을 볼 수 있다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
div:has(#absolute, #perfect) {
color: red;
}
</style>
</head>
<body>
<div><span>Document</span></div>
<div id="absolute" class="docu2"><span>Document</span></div>
<div id="perfect"><span>Document</span></div>
</body>
</html>

div:where(#absolute, #perfect) {
color: red;
}

레퍼런스