...
: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() 선택자를 통해 보다 모던스럽게 css 코드를 구성할 수 있다. 불필요한 중복을 없애 코드 자체도 짧아지게 되고, 각 선택자를 개별적으로 분석하기 때문에 만일 잘못된 선택자가 있어도 이해가 안되면 그대로 버려버려 확장성도 좋다.
:where(nav > ul, footer > ol, aside > p) a:hover {
color: purple;
text-decoration: underline wavy deeppink;
}
See the Pen :where 선택자 by barzz12 (@inpaSkyrim) on CodePen.
이중 :where() 처리
좀 더 이 선택자의 강력한 효과를 확연히 느낄수 있게 좀 길다란 예시 코드를 가져와봤다.
다음 선택자들을 보면 section, article, aside, nav 선택자와 그의 자손인 h1 ~ h6 선택자가 반복되는 걸 볼 수 있다. 이를 이중 :where() 을 통해 획기적으로 줄일 수가 있다.
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 선택자와 동일하게 동작된다.
단, 유일한 차이점은 명시도 에 있다. id 선택자는 다른 선택자보다 우선순위가 가장 높고, css 코드가 아래일 수록 우선순위가 적용된다고 들어본적이 있을 것인데 이것을 말하는 것이다. 즉, :where()는 명시도가 0이지만 :is()는 명시도가 가장 높다.
:where()와 :is() 차이점
아래 예제 코드를 보면 <header> , <main> , <footer> 안의 글자의 색을 스타일링 하려 한다. 이때 각각 :where() 와 :is() 로 글자색을 지정하고 css 마지막에 전체 선택자로 글자색을 녹색으로 덮어 씌웠다. 결과는 어떻게 될까?
<article>
<h2>:where()</h2>
<header class="where">
<p>Section</p>
</header>
<main class="where">
<p>Aside</p>
<main/>
<footer class="where">
<p>Footer</p>
</footer>
</article>
<article>
<h2>:is()</h2>
<header class="is">
<p>Section</p>
</header>
<main class="is">
<p>Aside</p>
</main>
<footer class="is">
<p>Footer</p>
</footer>
</article>
/* 맨 아래 정의된 선택자에 의해 무시된다 */
:where(header.where, main.where, footer.where) p {
color: red;
}
/* 가장 명시도가 높아 밑에 뭐가 있던 무조건 적용된다 */
:is(header.is, main.is, footer.is) p {
color: orange;
}
/* 전체 선택자 처리 */
header p,
main p,
footer p {
color: green;
}
See the Pen :is() 선택자 by barzz12 (@inpaSkyrim) on CodePen.
:where() 은 명시도가 0이기 때문에 css 마지막에 선언된 전체 선택자에 의해 효과가 덮어씌워졌다. 그러나 :is()는 명시도가 높아 가장 높은 우선순위를 가지기 때문에 뒤에 뭐가 오든 무조건 해당 스타일로 적용되게 된다.
:not() 가상 선택자
이 선택자는 이름 그대로 :not() 괄호 안의 매개변수 요소를 제외한 모든 요소의 스타일을 지정하는 데 사용된다.
:not() 을 이용하면 솔루션에 대해서 좀더 심플하게 해결책을 구성할 수 가 있는데, 예를 들어 다음 리스트 박스 마다 간격을 주기 위해 margin-bottom을 설정하였다고 하자.
<div class="card">
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
</ul>
</div>
ul li {
margin-bottom: 20px;
}
See the Pen :not 선택자 by barzz12 (@inpaSkyrim) on CodePen.
하지만 모든 리스트 하단에 margin이 들어가 박스가 대칭이 이루어지지 않는 문제점이 있다.
margin-top도 설정하는 법이나 강제로 li 요소 마지막은 margin을 설정하지 않도록 하는 방법도 있겠지만 좀더 심플하고 코드 한줄로 해결할 수가 있다.
/* li 요소 마지막은 적용하지 않는다 */
ul li:not(:last-of-type) {
margin-bottom: 20px;
}
See the Pen Untitled by barzz12 (@inpaSkyrim) on CodePen.
이밖에도 이를 응용해 속성 선택자에서 지원안하는 != not equals 연산자 기능을 구현할 수도 있다.
:has() 가상 선택자
:has() 는 최근에 추가된 선택자이다. 이 선택자는 해당 부모 선택자가 특정 자식 선택자를 가지고 있을 경우 적용하는 이른바 css의 if문 버전이라 보면 된다. 기존에는 이를 구현하려면 css로만은 문법적인 한계가 있어서 SCSS나 자바스크립트를 통해서만 가능했다. 하지만 :has() 개념은 굉장히 유용하고 편리한 선택자 개념이라 jQuery에서 제이쿼리 전용 선택자으로만 쓰였지만, 얼마전 드디어 공식화 되었다.
사용법은 예를들어 div.parent 가 만일 p 요소를 가지고 있는 경우에만 자신의 요소에 스타일을 적용하고 싶다면 다음과 같다.
<div class="parent">
<p>Child</p>
</div>
/* div 요소가 자식으로 p를 가지고 있을 경우 스타일 적용 */
div:has(> p) {
background: red;
}
:has() 선택자 조합 종류
:has() 괄호 안 매개변수로 선택자 문법을 넣음으로써 자식, 형제, 자손 선택자를 다양한 폭으로 지정해줄 수 있다.
/* a 요소가 자손으로 img를 가지고 있을 경우 */
a:has(img) {
}
/* a 요소가 자식으로 img를 가지고 있을 경우 */
a:has(> img) {
}
/* h3 요소가 형제로 p를 가지고 있을 경우 */
h3:has(+ p) {
}
/* article 요소가 자손으로 h3 그리고 p를 가지고 있을 경우 (and 논리) */
article:has(h3, p) {
}
:has() 와 :not() 조합
이번엔 약간 난이도 높은 조합을 가져와 보았다. :has() 와 :not() 가상 요소를 어떤 순서대로 조합하느냐에 따라 논리적으로 완전히 달라지기 때문에, 어떻게 되는지 코드를 보고 추론해보자.
/* div 요소가 자손으로 h3을 가지고 있지 않을 경우 전체 칼라 적용 */
div.section1:not(:has(h3)) {
color: red;
}
/* div 요소의 자손이 h3이 아닌 요소가 있을 경우 전체 칼라 적용 */
div.section2:has(:not(h3)) {
color: blue;
}
See the Pen :has() 와 :not() 조합 by barzz12 (@inpaSkyrim) on CodePen.
자바스크립트 셀렉터 조합
: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)')
브라우저가 :has() 를 지원하는지 테스트
단, 최신 스펙인 만큼 아직 파이어폭스나 인터넷 익스플로러에서는 아직 사용할 수 없다는 단점이 있다.
만일 해당 브라우저가 이 선택자를 지원하는지 알아보고 우회 방법을 적용하고 싶다면 @supports 를 통해 구성해주면 된다.
@supports(selector(:has(p))) {
/* Supported! */
}
@supports not (selector(:has(p))) {
/* Not Supported! 우회코드 작성 */
}
# 참고자료
https://junghan92.medium.com/%EB%B2%88%EC%97%AD-where-is-has-%EB%8B%B9%EC%8B%A0%EC%9D%98-%EC%82%B6%EC%9D%84-%EB%8D%94-%EC%89%BD%EA%B2%8C-%EB%A7%8C%EB%93%A4%EC%96%B4-%EC%A4%84-%EC%83%88%EB%A1%9C%EC%9A%B4-css-%EC%84%A0%ED%83%9D%EC%9E%90-ee44dd58aa3b
https://css-tricks.com/complete-guide-to-css-functions/
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.