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>

.different 클래스가 있는 요소의 하위 요소인 <x-foo>의 스타일을 지정하려면 :host-context(.different)를 사용하면 됩니다.

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

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

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

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

: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 검을 사용하는 것과 같습니다. Shadow DOM의 경계를 관통하여 Shadow 트리 내의 요소에 스타일을 지정할 수 있습니다.

::shadow 의사 요소

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

예를 들어 요소가 그림자 루트를 호스팅하는 경우 #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의 수준이 여러 개 있는 것이 일반적인 Custom Elements 환경에서 특히 유용합니다. 대표적인 예로는 여러 개의 맞춤 요소 (각각 자체 그림자 트리를 호스팅함)를 중첩하거나 <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를 타겟팅하여 슬라이더 썸네일 <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>

스타일 재설정

글꼴, 색상, line-height와 같은 상속 가능한 스타일은 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에 배포되면 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 변수 자리표시자를 포함하여 작성자는 서드 파티에 편리한 스타일 지정 후크를 제공하여 콘텐츠를 추가로 맞춤설정할 수 있습니다. 요컨대 웹 작성자는 콘텐츠가 표시되는 방식을 완전히 제어할 수 있습니다.