Shadow DOM 201

CSS 및 스타일 지정

이 글에서는 Shadow DOM으로 할 수 있는 놀라운 일들을 설명합니다. Shadow DOM 101에서 설명한 개념을 기반으로 합니다. 소개를 찾고 있는 경우 해당 도움말을 참조하세요.

소개

현실을 직시해야 합니다. 스타일이 지정되지 않은 마크업에는 매력적이지 않습니다. 다행히 웹 구성요소 개발자분들이 이를 미리 확인하여 주저하지 않았습니다. CSS 범위 지정 모듈은 섀도우 트리에서 콘텐츠 스타일을 지정하는 다양한 옵션을 정의합니다.

스타일 캡슐화

Shadow DOM의 핵심 기능 중 하나는 섀도 경계입니다. 멋진 속성이 많이 있지만 가장 좋은 점 중 하나는 스타일 캡슐화를 무료로 제공한다는 것입니다. 다른 말로 표현하면 다음과 같습니다.

<div><h3>Light DOM</h3></div>
<script>
var root = document.querySelector('div').createShadowRoot();
root.innerHTML = `
  <style>
    h3 {
      color: red;
    }
  </style>
  <h3>Shadow DOM</h3>
`;
</script>

이 데모에는 두 가지 흥미로운 점이 있습니다.

  • 이 페이지에는 다른 h3가 있지만 h3 선택기와 일치하여 빨간색으로 스타일이 지정된 유일한 h3은 ShadowRoot에 있는 h3입니다. 다시 말씀드리지만, 스타일 범위가 기본적으로 지정되었습니다.
  • h3를 타겟팅하는 이 페이지에 정의된 다른 스타일 규칙은 내 콘텐츠에 표시되지 않습니다. 선택기가 섀도우 경계를 넘지 않기 때문입니다.

스토리의 교훈 외부 세계의 스타일 캡슐화가 있습니다. Shadow DOM님, 감사합니다.

호스트 요소의 스타일 지정

:host를 사용하면 섀도우 트리를 호스팅하는 요소를 선택하고 스타일을 지정할 수 있습니다.

<button class="red">My Button</button>
<script>
var button = document.querySelector('button');
var root = button.createShadowRoot();
root.innerHTML = `
  <style>
    :host {
      text-transform: uppercase;
    }
  </style>
  <content></content>
`;
</script>

한 가지 문제는 상위 페이지의 규칙이 요소에 정의된 :host 규칙보다 특정성은 높지만 호스트 요소에 정의된 style 속성보다는 특이성이 낮다는 것입니다. 이렇게 하면 사용자가 외부에서 스타일을 재정의할 수 있습니다. :host는 또한 ShadowRoot의 컨텍스트에서만 작동하므로 Shadow DOM 외부에서 사용할 수 없습니다.

:host(<selector>)의 기능적 형태를 사용하면 <selector>와 일치하는 경우 호스트 요소를 타겟팅할 수 있습니다.

- 요소 자체에 클래스 .different (예: <x-foo class="different"></x-foo>)가 있는 경우에만 일치합니다.

:host(.different) {
    ...
}

사용자 상태에 반응

:host의 일반적인 사용 사례는 맞춤 요소를 만들고 다양한 사용자 상태 (:hover, :focus, :active 등)에 반응하려는 경우입니다.

<style>
  :host {
    opacity: 0.4;
    transition: opacity 420ms ease-in-out;
  }
  :host(:hover) {
    opacity: 1;
  }
  :host(:active) {
    position: relative;
    top: 3px;
    left: 3px;
  }
</style>

요소 테마 설정

:host-context(<selector>) 유사 클래스는 호스트 요소 또는 상위 요소가 <selector>와 일치하는 경우 호스트 요소와 일치합니다.

:host-context()의 일반적인 용도는 주변을 기반으로 요소의 테마를 설정하는 것입니다. 예를 들어 많은 사용자가 <html> 또는 <body>에 클래스를 적용하여 테마를 설정합니다.

<body class="different">
  <x-foo></x-foo>
</body>

<x-foo>.different 클래스가 있는 요소의 하위 요소일 때 :host-context(.different)를 사용하여 스타일을 지정할 수 있습니다.

:host-context(.different) {
  color: red;
}

이렇게 하면 요소의 Shadow DOM에서 컨텍스트에 따라 고유한 스타일을 지정하는 스타일 규칙을 캡슐화할 수 있습니다.

하나의 섀도 루트 내에서 여러 호스트 유형 지원

테마 설정 라이브러리를 만들고 동일한 Shadow DOM 내에서 여러 유형의 호스트 요소 스타일 지정을 지원하려는 경우에도 :host를 사용할 수 있습니다.

:host(x-foo) {
    /* Applies if the host is a <x-foo> element.*/
}

:host(x-foo:host) {
    /* Same as above. Applies if the host is a <x-foo> element. */
}

:host(div) {
    /* Applies if the host element is a <div>. */
}

외부에서 Shadow DOM 내부 스타일 지정

::shadow 의사 요소와 /deep/ 연결자는 CSS 권한의 Vorpal 검을 가진 것과 같습니다. 이 API를 사용하면 Shadow DOM의 경계를 관통하여 섀도우 트리 내의 요소에 스타일을 지정할 수 있습니다.

::shadow pseudo-element

요소에 섀도우 트리가 하나 이상 있는 경우 ::shadow 유사 요소는 섀도우 루트 자체와 일치합니다. 요소의 섀도우 돔 내부에 있는 노드의 스타일을 지정하는 선택기를 작성할 수 있습니다.

예를 들어 요소가 섀도 루트를 호스팅하는 경우 #host::shadow span {}를 작성하여 섀도우 트리 내의 모든 스팬의 스타일을 지정할 수 있습니다.

<style>
  #host::shadow span {
    color: red;
  }
</style>

<div id="host">
  <span>Light DOM</span>
</div>

<script>
  var host = document.querySelector('div');
  var root = host.createShadowRoot();
  root.innerHTML = `
    <span>Shadow DOM</span>
    <content></content>
  `;
</script>

(맞춤 요소) - <x-tabs>의 Shadow DOM에 하위 요소가 <x-panel>개 있습니다. 각 패널은 h2 제목이 포함된 자체 섀도우 트리를 호스팅합니다. 기본 페이지에서 이러한 머리글의 스타일을 지정하려면 다음과 같이 작성할 수 있습니다.

x-tabs::shadow x-panel::shadow h2 {
    ...
}

/deep/ 컴비네이터

/deep/ 결합자는 ::shadow와 비슷하지만 더 강력합니다. 모든 그림자 경계를 완전히 무시하고 임의 개수의 그림자 트리에 교차합니다. 간단히 말해 /deep/를 사용하면 요소의 내장을 자세히 분석하고 모든 노드를 타겟팅할 수 있습니다.

/deep/ 결합자는 여러 수준의 Shadow DOM이 있는 맞춤 요소의 환경에서 특히 유용합니다. 대표적인 예로는 여러 맞춤 요소를 중첩하거나 (각각 자체 섀도우 트리 호스팅) <shadow>를 사용하여 다른 요소에서 상속하는 요소를 만드는 것입니다.

(맞춤 요소) - 트리의 아무 곳에서나 <x-tabs>의 하위 요소인 모든 <x-panel> 요소를 선택합니다.

x-tabs /deep/ x-panel {
    ...
}

- 섀도우 트리의 모든 위치에서 .library-theme 클래스를 사용하여 모든 요소의 스타일을 지정합니다.

body /deep/ .library-theme {
    ...
}

querySelector() 작업

.shadowRoot가 DOM 순회를 위해 섀도우 트리를 여는 것처럼 조합자는 선택기 순회를 위해 섀도우 트리를 엽니다. 광기의 중첩된 연쇄를 작성하는 대신 단일 문을 작성할 수 있습니다.

// No fun.
document.querySelector('x-tabs').shadowRoot
        .querySelector('x-panel').shadowRoot
        .querySelector('#foo');

// Fun.
document.querySelector('x-tabs::shadow x-panel::shadow #foo');

네이티브 요소의 스타일 지정

기본 HTML 컨트롤은 스타일을 지정하는 데 어려움이 있습니다. 많은 사람들이 그저 포기하고 자신의 실수를 굴립니다. 그러나 ::shadow/deep/를 사용하면 Shadow DOM을 사용하는 웹 플랫폼의 모든 요소에 스타일을 지정할 수 있습니다. <input> 유형과 <video>가 좋은 예입니다.

video /deep/ input[type="range"] {
  background: hotpink;
}

스타일 후크 만들기

맞춤설정이 좋습니다. 경우에 따라 그림자의 스타일 지정 방패에 구멍을 뚫고 다른 사용자가 스타일을 지정할 수 있도록 후크를 만들어야 할 수 있습니다.

::shadow 및 /deep/ 사용

/deep/에는 강력한 기능이 있습니다. 구성요소 작성자는 개별 요소를 스타일 지정 가능으로 지정하거나 수많은 요소를 테마 가능으로 지정할 수 있습니다.

- .library-theme 클래스가 있는 모든 요소의 스타일을 지정하고 모든 섀도우 트리를 무시합니다.

body /deep/ .library-theme {
    ...
}

맞춤 유사 요소 사용

WebKitFirefox 모두 네이티브 브라우저 요소의 내부 부분에 스타일을 지정하는 유사 요소를 정의합니다. input[type=range]를 예로 들 수 있습니다. ::-webkit-slider-thumb를 타겟팅하여 슬라이더 thumb <span style="color:blue">blue</span>의 스타일을 지정할 수 있습니다.

input[type=range].custom::-webkit-slider-thumb {
  -webkit-appearance: none;
  background-color: blue;
  width: 10px;
  height: 40px;
}

브라우저가 일부 내부 요소에 스타일 지정 후크를 제공하는 방식과 마찬가지로 Shadow DOM 콘텐츠 작성자는 특정 요소를 외부자가 스타일을 지정할 수 있도록 지정할 수 있습니다. 이는 맞춤 유사 요소를 통해 이루어집니다.

pseudo 속성을 사용하여 요소를 맞춤 유사 요소로 지정할 수 있습니다. 해당 값 또는 이름은 'x-' 접두사를 붙여야 합니다. 이렇게 하면 섀도우 트리에서 해당 요소와의 연결이 생성되고 외부 사용자에게 섀도우 경계를 가로지르는 지정된 레인이 제공됩니다.

다음은 맞춤 슬라이더 위젯을 만들고 사용자가 슬라이더의 엄지 손가락 블루 스타일을 지정할 수 있도록 하는 예입니다.

<style>
  #host::x-slider-thumb {
    background-color: blue;
  }
</style>
<div id="host"></div>
<script>
  var root = document.querySelector('#host').createShadowRoot();
  root.innerHTML = `
    <div>
      <div pseudo="x-slider-thumb"></div>' +
    </div>
  `;
</script>

CSS 변수 사용

테마 설정 후크를 만드는 효과적인 방법은 CSS 변수를 사용하는 것입니다. 기본적으로 다른 사용자가 채울 '스타일 자리표시자'를 만듭니다.

Shadow DOM에서 변수 자리표시자를 표시하는 맞춤 요소 작성자를 상상해 보세요. 하나는 내부 버튼의 글꼴 스타일 지정을 위한 것이고 다른 하나는 색상에 대한 스타일입니다.

button {
  color: var(--button-text-color, pink); /* default color will be pink */
  font-family: var(--button-font);
}

그런 다음, 요소의 임베딩이 해당 값을 원하는 대로 정의합니다. 자신의 페이지에 멋진 Comic Sans 테마를 적용할 수도 있습니다.

#host {
  --button-text-color: green;
  --button-font: "Comic Sans MS", "Comic Sans", cursive;
}

CSS 변수가 상속받는 방식 때문에 모든 것이 아름답고 잘 작동합니다. 전체 그림은 다음과 같습니다.

<style>
  #host {
    --button-text-color: green;
    --button-font: "Comic Sans MS", "Comic Sans", cursive;
  }
</style>
<div id="host">Host node</div>
<script>
  var root = document.querySelector('#host').createShadowRoot();
  root.innerHTML = `
    <style>
      button {
        color: var(--button-text-color, pink);
        font-family: var(--button-font);
      }
    </style>
    <content></content>
  `;
</script>

스타일 재설정

글꼴, 색상, 선 높이와 같은 상속 가능한 스타일은 계속해서 Shadow DOM의 요소에 영향을 줍니다. 그러나 유연성을 최대화하기 위해 Shadow DOM은 resetStyleInheritance 속성을 제공하여 섀도우 경계에서 발생하는 작업을 제어합니다. 새 구성요소를 만들 때 새로 시작하는 방법이라고 생각하면 됩니다.

resetStyleInheritance

  • false - 기본값입니다. 상속 가능한 CSS 속성이 계속 상속됩니다.
  • true - 상속 가능한 속성을 경계에서 initial로 재설정합니다.

다음은 resetStyleInheritance를 변경하면 섀도우 트리가 어떤 영향을 받는지 보여주는 데모입니다.

<div>
  <h3>Light DOM</h3>
</div>

<script>
  var root = document.querySelector('div').createShadowRoot();
  root.resetStyleInheritance = <span id="code-resetStyleInheritance">false</span>;
  root.innerHTML = `
    <style>
      h3 {
        color: red;
      }
    </style>
    <h3>Shadow DOM</h3>
    <content select="h3"></content>
  `;
</script>

<div class="demoarea" style="width:225px;">
  <div id="style-ex-inheritance"><h3 class="border">Light DOM</div>
</div>
<div id="inherit-buttons">
  <button id="demo-resetStyleInheritance">resetStyleInheritance=false</button>
</div>

<script>
  var container = document.querySelector('#style-ex-inheritance');
  var root = container.createShadowRoot();
  //root.resetStyleInheritance = false;
  root.innerHTML = '<style>h3{ color: red; }</style><h3>Shadow DOM<content select="h3"></content>';

  document.querySelector('#demo-resetStyleInheritance').addEventListener('click', function(e) {
    root.resetStyleInheritance = !root.resetStyleInheritance;
    e.target.textContent = 'resetStyleInheritance=' + root.resetStyleInheritance;
    document.querySelector('#code-resetStyleInheritance').textContent = root.resetStyleInheritance;
  });
</script>
DevTools 상속된 속성

.resetStyleInheritance를 이해하는 것은 약간 까다롭습니다. 주된 이유는 상속 가능한 CSS 속성에만 영향을 주기 때문입니다. 상속할 속성을 찾을 때 페이지와 ShadowRoot 사이의 경계에서 호스트에서 값을 상속하지 않고 대신 initial 값을 사용합니다 (CSS 사양에 따라).

CSS에서 어떤 속성이 상속되었는지 확실하지 않다면 이 편리한 목록을 확인하거나 요소 패널에서 '상속 항목 표시' 체크박스를 선택 해제하세요.

분산 노드 스타일 지정

분산 노드는 삽입 지점 (<content> 요소)에서 렌더링되는 요소입니다. <content> 요소를 사용하면 Light DOM에서 노드를 선택하고 Shadow DOM의 사전 정의된 위치에 렌더링할 수 있습니다. 이들은 논리적으로 Shadow DOM에 있지 않으며 여전히 호스트 요소의 하위 요소입니다. 삽입 지점은 단순히 렌더링하는 것입니다.

분산 노드에는 기본 문서의 스타일이 유지됩니다. 즉, 삽입 지점에서 렌더링하더라도 기본 페이지의 스타일 규칙이 요소에 계속 적용됩니다. 다시 말하지만, 분산 노드는 여전히 논리적으로 가벼운 DOM에 있으며 이동하지 않습니다. 다른 곳에서는 렌더링만 됩니다. 그러나 노드가 Shadow DOM에 분산되면 섀도우 트리 내에 정의된 추가 스타일을 사용할 수 있습니다.

::content 가상 요소

분산 노드는 호스트 요소의 하위 노드이므로 Shadow DOM 내에서 어떻게 타겟팅할 수 있나요? 정답은 CSS ::content 의사 요소입니다. 삽입 지점을 통과하는 Light DOM 노드를 타겟팅하는 방법입니다. 예를 들면 다음과 같습니다.

::content > h3는 삽입점을 통과하는 모든 h3 태그의 스타일을 지정합니다.

예를 살펴보겠습니다.

<div>
  <h3>Light DOM</h3>
  <section>
    <div>I'm not underlined</div>
    <p>I'm underlined in Shadow DOM!</p>
  </section>
</div>

<script>
var div = document.querySelector('div');
var root = div.createShadowRoot();
root.innerHTML = `
  <style>
    h3 { color: red; }
      content[select="h3"]::content > h3 {
      color: green;
    }
    ::content section p {
      text-decoration: underline;
    }
  </style>
  <h3>Shadow DOM</h3>
  <content select="h3"></content>
  <content select="section"></content>
`;
</script>

삽입점에서 스타일 재설정

ShadowRoot를 만들 때 상속된 스타일을 재설정하는 옵션이 있습니다. <content><shadow> 게재 포인트에도 이 옵션이 있습니다. 이러한 요소를 사용하는 경우 JS에서 .resetStyleInheritance를 설정하거나 요소 자체에 불리언 reset-style-inheritance 속성을 사용하세요.

  • ShadowRoot 또는 <shadow> 삽입점의 경우 reset-style-inheritance은 상속 가능한 CSS 속성이 섀도우 콘텐츠에 도달하기 전에 호스트에서 initial로 설정되었음을 의미합니다. 이 위치를 상한 경계라고 합니다.

  • <content> 삽입점의 경우: reset-style-inheritance은 상속 가능한 CSS 속성이 initial로 설정된 후 호스트의 하위 요소가 삽입점에 배포된다는 의미입니다. 이 위치를 하한 경계라고 합니다.

결론

맞춤 요소의 작성자에게는 콘텐츠의 디자인과 분위기를 제어할 수 있는 수많은 옵션이 있습니다. Shadow DOM은 이 용감하고 새로운 세계의 기반을 형성합니다.

Shadow DOM은 범위가 지정된 스타일 캡슐화와 원하는 만큼 외부 세상을 들여다볼 수 있는 수단을 제공합니다. 맞춤 유사 요소를 정의하거나 CSS 변수 자리표시자를 포함하여 작성자는 콘텐츠를 추가로 맞춤설정할 수 있는 편리한 스타일 지정 후크를 서드 파티에 제공할 수 있습니다. 대체로 웹 작성자는 콘텐츠가 표시되는 방식을 전적으로 제어할 수 있습니다.