쉐도우 돔을 스타일링 하는 방법

쉐도우 돔을 스타일링 하는 방법

HTML은 모두 DOM으로 변환된다. DOM은 자바스크립트로 엑세스하고 CSS로 스타일링 할 수 있다. Shadow DOM은 요소 내부에 Shadow DOM 이라는 자체 DOM을 생성할 수 있다.

웹 사이트의 소스를 보다가 위와 같은 것을 발견한다면 그것이 쉐도우 돔이 적용된 것이다.

Shadow DOM을 사용하는 이유?

  • 페이지의 다른 자바스크립트나 CSS로 부터 컴포넌트를 보호
  • 컴포넌트에 대한 캡슐화된 스타일을 만들 수 있음
  • 컴포넌트 내부 요소에 안전하게 ID를 사용할 수 있음


Shadow DOM 생성

const shadow = document.getElementById('shadow');
const shadowRoot = shadow.attachShadow({ mode: 'closed' });

쉐도우 돔은 위와 같이 간단하게 생성할 수 있다. mode는 open, closed 2가지 값으로 설정할 수 있는데 두 옵션의 차이는 다음과 같다.

// open
const shadowRoot = shadow.attachShadow({ mode: 'open' });
shadow.shadowRoot // 접근 가능 (= shadowRoot)

// closed
const shadowRoot = shadow.attachShadow({ mode: 'open' });
shadow.shadowRoot // 접근 불가 (= null)


Sadow DOM 스타일링

쉐도우 돔은 기본적으로 외부 스타일에 영향을 받지 않는다. 예를들어 아래와 같은 코드가 있다고 가정하면

<style>
    span {
        color: red;
     }
</style>

<span>Light DOM Content</span>
<div id="shadow"></div>
<script>
    const shadow = document.getElementById('shadow');
    const shadowRoot = shadow.attachShadow({ mode: 'closed' });
    shadowRoot.innerHTML = `
        <span>Shadow DOM Content</span>
    `;
</script>

결과는 위와 같이 출력된다. 다만 일부 상황에서는 쉐도우 돔과 라이트 돔이 스타일을 공유하거나 영향을 줄 수 있다. 먼저 라이트 돔의 스타일이 쉐도우 돔의 영향을 주는 케이스다.


라이트 -> 쉐도우

1. 스타일 상속

Light DOM에서 Shadow DOM이 생성된 엘리먼트(Host)로 스타일이 상속될 경우 Shadow DOM에도 스타일이 반영된다.

<style>
    body {
        color: blue;
    }
    span {
        color: red;
     }
</style>

<span>Light DOM Content</span>
<div id="shadow"></div>
<script>
    const shadow = document.getElementById('shadow');
    const shadowRoot = shadow.attachShadow({ mode: 'closed' });
    shadowRoot.innerHTML = `
        <span>Shadow DOM Content</span>
    `;
</script>

body의 스타일은 Shadow DOM의 호스트에도 상속되기 때문에 결과는 아래와 같아진다.

Shadow DOM에서 이러한 상속을 모두 제한하려는 경우 아래와 같이 호스트의 상속을 막을 수 있다.

shadowRoot.innerHTML = `
    <style> :host { all: initial; } </style>
    <span>Shadow DOM Content</span>
`;

그럼 다시 결과는 원점으로 돌아간다.

2. 사용자 정의 속성

:root에 정의된 사용자 정의 속성(variant)은 Shadow DOM에서도 사용할 수 있다. 상속으로 스타일을 주입하는 것 보다 훨씬 더 예측 가능하고 사이드 이펙트가 적다.

<style>
    :root {
        --my-favorite-color: #735af2;
    }
    span {
        color: var(--my-favorite-color);
    }
</style>

<span>Light DOM Content</span>
<div id="shadow"></div>
<script>
    const shadow = document.getElementById('shadow');
    const shadowRoot = shadow.attachShadow({ mode: 'closed' });
    shadowRoot.innerHTML = `
        <style>
            span {
                color: var(--my-favorite-color);
            }
        </style>
        <span>Shadow DOM Content</span>
    `;
</script>

3, 의사 요소

part 속성을 이용해서 외부에서 Shadow DOM에 스타일을 주입할 수 있도록 할 수 있다.

<style>
    span {
        color: #735af2;
    }
    #shadow::part(shadow-content) {
        color: #735af2;
    }
</style>

<span>Light DOM Content</span>
<div id="shadow"></div>
<script>
    const shadow = document.getElementById('shadow');
    const shadowRoot = shadow.attachShadow({ mode: 'closed' });
    shadowRoot.innerHTML = `
        <span part="shadow-content">Shadow DOM Content</span>
    `;
</script>

4. 템플릿 슬롯

커스텀 엘리먼트를 선언할 때 사용하는 슬롯을 사용할 때 스타일은 의도한 것과 다르게 들어가므로 유의해야 한다. Shadow DOM 내부에서 스타일이 주입되는 것이 아니라 외부에서 스타일이 주입되는 것에 유의해야 한다.

<style>
    p {
        color: blue;
    }
</style>

<template id="my-component-template">
    <style>
        p {
            color: red;
        }
    </style>
    <div>
        <slot></slot>
    </div>
</template>

<my-component>
    <p>External content</p>
</my-component>

<script>
    class MyComponent extends HTMLElement {
        constructor() {
            super();
            this.attachShadow({ mode: 'open' });
            const template = document.querySelector('#my-component-template');
            this.shadowRoot.appendChild(template.content.cloneNode(true));
        }
    }

    customElements.define('my-component', MyComponent);
</script>


쉐도우

1. 템플릿 슬롯

템플릿 내부에서 slot의 스타일을 지정하려는 경우, ::slotted 선택자를 사용하여 스타일을 적용할 수 있다.

<style>
    p {
        color: blue;
    }
</style>

<template id="my-component-template">
    <style>
        ::slotted(p) {
            color: red !important;
        }
    </style>
    <div>
        <slot></slot>
    </div>
</template>

<my-component>
    <p>External content</p>
</my-component>

2. 호스트

위에서 잠시 언급이 되었지만 :host 선택자를 사용하면 쉐도우 돔을 렌더하고 있는 호스트의 스타일링을 해줄 수 있다.

<style>
    span {
        color: red;
    }
</style>

<span>Light DOM Content</span>
<div id="shadow"></div>
<script>
    const shadow = document.getElementById('shadow');
    const shadowRoot = shadow.attachShadow({ mode: 'closed' });
    shadowRoot.innerHTML = `
        <style>
            :host {
                color: blue;
            }
        </style>
        <span>Shadow DOM Content</span>
    `;
</script>

:host-context 선택자를 사용하면 호스트의 부모 요소의 적용된 클래스를 참조하여 스타일링 할 수 있다. 예를들어 다크 모드와 같은 기능을 만들 때 특히 유용할 수 있다. 참고로 해당 글 작성일 기준으로 사파리와 파이어폭스에서는 동작하지 않는 기능이다.

<style>
      body.dark {
        background: black;
    }
    span {
        color: red;
    }
</style>

<span>Light DOM Content</span>
<div id="shadow"></div>
<script>
    document.body.classList.add('dark');

    const shadow = document.getElementById('shadow');
    const shadowRoot = shadow.attachShadow({ mode: 'open' });
    shadowRoot.innerHTML = `
        <style>
            :host-context(body.dark) span {
                color: white;
            }
        </style>
        <span>Shadow DOM Content</span>
    `;
</script>

3. 스타일 임포트

Light DOM에서 Global로 사용하는 스타일이 있는 경우 간단하게 import 구문을 사용할 수 있다. 별도의 라이브러리나 별도의 처리 없이 브라우저 네이티브로 동작하며, 이미 다운로드된 스타일 시트의 경우 캐시되어 불필요한 리소스가 낭비되지 않는다.

<template id="my-component-template">
    <style>
        @import '/assets/style/normalize.css';
    </style>
    <div>
        <slot></slot>
    </div>
</template>
4. 생성 가능한 스타일 시트

Constructable Stylesheets는 Shadow DOM에서 공유 가능한 스타일시트를 제공하여, 재사용성과 성능을 극대화할 수 있다. 또한 엔드 유저에게 별도의 스타일이 노출되지 않는다.

<style>
    span {
        color: red;
    }
</style>

<span>Light DOM Content</span>
<div id="shadow"></div>
<script>
    const shadow = document.getElementById('shadow');
    const shadowRoot = shadow.attachShadow({ mode: 'open' });
    const styleSheet = new CSSStyleSheet();
    styleSheet.replace(`
        span {
            color: blue;
        }
    `)
    shadowRoot.adoptedStyleSheets = [styleSheet];
    shadowRoot.innerHTML = `
        <span>Shadow DOM Content</span>
    `;
</script>


참고 자료

이 글이 도움이 되었나요?

신고하기
0분 전
작성된 댓글이 없습니다. 첫 댓글을 달아보세요!
    댓글을 작성하려면 로그인이 필요합니다.