원문: https://jordanbrennan.hashnode.dev/8-ways-to-style-the-shadow-dom
과거에는 Shadow DOM을 사용한다는 것은 CSS 유지보수 측면의 이점을 희생하는 것을 의미했습니다. 하지만 이제는 그렇지 않습니다.
먼저 Shadow DOM에 대해 간단히 알아본 후, Shadow DOM을 스타일링 하는 8가지 방법을 알아보겠습니다.
HTML은 DOM이 됩니다. DOM에 익숙하지 않다면 W3Schools의 html DOM 소개를 참조하세요.
DOM은 자바스크립트로 액세스하고 CSS로 스타일링 할 수 있습니다.
네이티브 HTML과 마찬가지로 사용자 정의 요소(Custom Element)도 DOM의 일부입니다.
사용자 정의 요소(실제로는 모든 요소)는 선택적으로 내부에 Shadow DOM이라는 자체 DOM을 생성할 수 있습니다.
Shadow DOM 콘텐츠는 mode
옵션을 사용하여 외부 자바스크립트, 즉 사용자 정의 요소 클래스 외부 JS에서의 액세스 여부를 설정할 수 있습니다. 반면에, Shadow DOM은 mode 옵션과 상관없이 외부 CSS에 액세스할 수 없습니다. 하지만 보이는게 전부는 아닙니다. 잠시 후에 이 문제를 해결하는 방법을 알아보겠습니다.
Shadow DOM이 유용한 이유는 다음과 같습니다.
사용자 정의 요소에서 Shadow DOM을 사용할 필요는 없지만, 스타일링과 관련된 최신 기능을 활용하면 사용하지 않을 이유가 없으므로 Shadow DOM으로 요소를 스타일링하는 옵션에 대해 자세히 알아보겠습니다.
Shadow DOM 스타일링에는 내부(Shadow root 요소 내부에서)와 외부(외부, 일반적인 "light" DOM에서)의 두 가지 일반적인 접근 방식이 있습니다. 외부부터 시작해서 내부로 들어가 보겠습니다. 다시 말하지만 mode
는 자바스크립트에만 적용되므로 여기서는 관련이 없습니다.
Mdash 전역 스타일시트를 예로 들어보겠습니다. 여기에는 유틸리티 클래스뿐만 아니라 모든 HTML 요소에 대한 수많은 스타일이 있습니다.
<link href="https://unpkg.com/m-@3.2.0/dist/m-.css" rel="stylesheet">
전역 스타일시트는 당연히 DOM에 적용되지만 Shadow DOM에도 적용되도록 할 수 있나요? 네, 네 가지 방법이 있습니다.
일부 글로벌 스타일은 실제로 Shadow DOM에 자동으로 적용되며, 기본적으로 상속됩니다.
스타일이 상속되어 요소에 적용되려면 부모로부터 스타일을 가져와야 합니다. 즉, DOM 트리를 따라 body나 html에 이르는 Shadow DOM의 모든 조상이 상속 가능한 속성을 명시적으로 설정해야 합니다. 상속 가능한 CSS 속성의 공식 목록은 여기에서 확인할 수 있습니다. 예를 들어 Mdash는 본문에서 기본 텍스트 색상을 설정합니다.
body {
color: var(--m-color-gray-7);
}
색상은 상속되는 속성이므로 Shadow DOM 내부의 요소를 포함하여 페이지의 모든 요소에 적용됩니다. 이 기능을 사용하기 위해 별도의 작업을 수행할 필요가 없다는 점을 명확히 할 필요가 있습니다. 이는 Shadow DOM의 경우에도 기본 CSS 동작입니다.
스타일을 상속하지 않으려면 다음과 같이 all: initial
을 사용하면 됩니다.
/* 웹 컴포넌트의 <style> 태그 */
:host {
all: initial; /* 모든 속성을 HTML 스펙의 기본값으로 되돌립니다 */
}
다음으로, :root
에 정의된 모든 CSS 사용자 정의 속성은 Shadow DOM에서 사용할 수 있습니다. 예를 들어, Shadow DOM을 사용한 웹 컴포넌트에서 디자인 시스템의 디자인 토큰에 접근하려고 할 때, 아래와 같이 할 수 있습니다.
/* 사용자 지정 속성이 포함된 전역 스타일 시트 */
:root {
--m-color-red-3: #a2204f;
}
/* 전역의 사용자 지정 속성은 웹 컴포넌트의 <style> 태그에서 사용 가능 */
p {
color: var(--m-color-red-3);
}
상속 가능한 스타일과 마찬가지로 별도의 작업이 필요하지 않습니다. 바로 사용 가능한 기본 제공 CSS 마법입니다! 이를 사용하면 글로벌 스타일을 쉽게 정의하고 선택적으로 적용할 수 있습니다 (예상치 못했거나 원하지 않는 경우가 있을 수 있는 상속과 달리).
Shadow DOM은 part
속성을 사용하여 글로벌 스타일에서 특정 요소를 이용할 수 있도록 만들 수 있습니다.
<!-- 웹 컴포넌트의 shadow DOM -->
<p part="intro">shadow root 밖에서 스타일될 수 있습니다.</p>
<p>shadow root 밖에서 스타일될 수 없습니다.</p>
그런 다음 CSS를 사용하여 intro
스타일을 지정할 수 있습니다.
my-element::part(intro) {
color: red;
}
마지막으로, 웹 컴포넌트 템플릿에서 스타일시트를 단순히 가져오는 것만으로도 모든 외부 스타일시트를 Shadow DOM에 노출할 수 있습니다.
/* 웹 컴포넌트의 <style> 태그 */
@import "https://unpkg.com/m-@3.2.0/dist/m-.css";
야호! 예상했던 것과 똑같이 작동합니다. Shadow DOM을 전혀 사용하지 않는 것과 비슷하지만 컴포넌트의 스타일이 외부로 새어나가지 않아 완벽합니다. 이것 좀 보세요.
<!--
웹 컴포넌트의 템플릿입니다.
.flex와 같은 유틸리티 클래스를 사용할 수 있으며
<a> 스타일과 같은 전역 요소 스타일이 적용됩니다.
-->
<template>
<header class="bg-white pad-lg brd-b">
<nav class="flex align-items-center gap-sm">
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/signup">Sign up</a>
</nav>
</header>
<style>
/* Mdash 디자인 시스템의 스타일을 가져옵니다 */
@import "https://unpkg.com/m-@3.2.0/dist/min.css";
/* 하지만 이 구성 요소의 스타일은 사라지지 않습니다 */
nav {
height: 50px;
}
</style>
</template>
여기서 중요한 점 세 가지는 다음과 같습니다.
그다지 유용하지는 않지만 그럼에도 불구하고 사용할 수 있는 또 다른 옵션은 생성 가능한 스타일시트입니다. 이는 light DOM 공간의 큰 CSS 문자열에서 프로그래밍 방식으로 스타일을 생성하여 하나 이상의 Shadow DOM 루트 또는 일반 DOM에 "차용"하기 위한 API입니다.
// light DOM 영역에서 새로운 스타일시트를"생성"합니다
const sheet = new CSSStyleSheet();
sheet.replaceSync('p { color: red; }'); // The CSS rules
// 생성한 스타일시트를 웹 컴포넌트의 Shadow 루트에 추가합니다
constructor() {
super();
this.attachShadow({ mode: 'open' }).appendChild(this.#template.content.cloneNode(true));
this.shadowRoot.adoptedStyleSheets.push(sheet); // 컴포넌트의 문단은 빨간색으로 표시됩니다
}
흥미로운 옵션이지만 매력적인 사용 사례는 찾지 못했습니다.
여기서 주목할 점은 슬롯은 항상 외부 CSS에 의해 스타일이 지정된다는 점, 즉 Shadow DOM 템플릿에서 선언하더라도 슬롯은 Shadow DOM이 아닌 light DOM이라는 점입니다.
지금까지 외부 CSS로 Shadow DOM의 스타일을 지정하는 5가지 방법을 살펴봤습니다. 이제 웹 컴포넌트 스스로 스타일을 지정할 수 있는 세 가지 방법을 살펴보겠습니다.
이 옵션은 아마도 가장 일반적인 옵션일 것입니다. Vue SFC, Riot 또는 Svelte를 좋아하신다면 이 옵션이 마음에 들 것입니다.
<template>
<style>
/* ::host는 사용자 정의 요소의 루트(예: <my-element>)입니다 */
:host {
background: blue;
color: white;
}
/* 다른 룰들은 이 사용자 정의 요소로 범위가 제한됩니다 */
p { color: red }
#name { color: lime }
</style>
<p>Hello, <div id="name"></div></p>
<div>
이 사용자 정의 요소의 배경은 파란색이고
이 div의 텍스트는 흰색입니다.
위의 단락은 빨간색이고 그 안의 div는 라임색입니다.
이 스타일 중 어느 것도 메인 페이지에 적용되지 않습니다.
</div>
</template>
<script>
customElements.define('my-element', class extend HTMLElement {
// 위에서 작성한 <template>을 사용합니다
})
</script>
Scoped 스타일은 웹 컴포넌트의 고유한 스타일이 페이지의 다른 요소에 영향을 미치지 않도록 보장합니다. 이는 네이티브 웹 컴포넌트에 바로 사용 가능한 매우 가치 있는 기능입니다. 반면 리액트는 그렇게 하지 못합니다!
스타일 속성을 사용하기 적당한 때와 장소가 있으며 웹 컴포넌트는 이것이 적합한 곳 중 하나입니다. 아래 예시에서는 첫 번째와 마지막 div를 대상으로 하는 선택자를 작성해 min-width를 설정하거나 이를 수행하는 클래스를 만들거나 심지어 id 선택자를 사용할 수도 있지만, 웹 컴포넌트에서는 이와 같은 스타일 추상화가 필요하지 않습니다. 인라인 스타일 속성은 이와 같은 특정 경우에 확실한 선택입니다.
<template>
<style>
:host { display: flex }
div { padding: 20px }
</style>
<div>Foo</div>
<div style="min-width: 100px">Bar</div>
<div style="min-width: 80px">Baz</div>
</template>
물론 사용자 정의 요소는 자바스크립트를 사용하여 요소의 스타일을 프로그래밍 방식으로 설정할 수 있습니다(예: this.querySelector('p').styles.color = 'red'
). 그리고 전역 사용자 정의 프로퍼티도 사용할 수 있다는 것을 잊지 마세요.
const css = getComputedStyle(document.body)
this.querySelector('p').styles.color = css.getPropertyValue('--m-color-red-3')
웹 컴포넌트는 이미 존재하며 결코 사라지지 않을 것입니다. 웹 컴포넌트는 성숙하고 광범위하게 지원되며, 가장 큰 앱을 보유한 대기업 중 일부가 사용하고 있습니다(YouTube의 DOM이나 Salesforce, Adobe 또는 SpaceX의 웹 컴포넌트를 살펴보세요!). 개발자 경험은 훌륭하며 리액트와 같은 프레임워크가 할 수 없는 작업도 수행할 수 있습니다. 그렇다고 웹 컴포넌트가 프레임워크를 대체할 수 있거나 대체해야 한다는 말은 아닙니다. 하지만, 프레임워크에 종속된 코드를 생각보다 많이 작성하고 있다는 것은 분명합니다. 여러분이 작성한 웹 컴포넌트 코드는 프레임워크로 구축된 어떤 코드보다 오래 사용할 수 있고 오래 지속될 것입니다.
과거에 몇 가지 오래된 끊김 현상 때문에 망설였다면 다시 시도해 보세요. API가 훨씬 개선되었으며, 특히 Shadow DOM 스타일링에 대한 모든 옵션이 추가되었습니다. 그리고 곧 출시될 리액트 19에서 리액트가 마침내 이를 지원하기로 합의함에 따라, 웹 컴포넌트 채택의 가장 큰 걸림돌은 마침내 사라질 것입니다. 이제 #useThePlatform 의 흐름에 동참할 시간입니다!
🚀 한국어로 된 프런트엔드 아티클을 빠르게 받아보고 싶다면 Korean FE Article(https://kofearticle.substack.com/)을 구독해주세요!
일부 글로벌 스타일이 자동으로 상속된다는 점을 명확히 함으로써, 흔히 혼동되는 부분을 명확하게 전달합니다. boxing random