Shadow DOM v1 - 자체 포함 웹 구성요소

웹 개발자는 Shadow DOM을 사용하여 웹 구성 요소에 대한 구획화된 DOM 및 CSS를 만들 수 있습니다.

요약

Shadow DOM은 웹 앱 빌드의 취약성을 제거합니다. 불안정함 HTML, CSS 및 JS의 전반적인 특성에서 비롯됩니다. 지난 몇 년 동안 Google은 무수히 많은 를 발명했다. / 도구 문제를 피할 수 있습니다 예를 들어 새 HTML ID/클래스를 사용하는 경우 페이지에서 사용 중인 기존 이름과 충돌할지 여부를 알 수 없습니다. 미묘한 버그가 튀어나오고, CSS 특수성은 큰 문제가 되고, 스타일은 !important이 중요합니다. 선택자를 통제할 수 없게 되고 성능이 저하될 수 있습니다. 목록 계속 진행됩니다.

Shadow DOM은 CSS 및 DOM을 수정합니다. 웹에 범위가 지정된 스타일이 도입되었습니다. 있습니다. 도구나 이름 지정 규칙 없이 CSS를 CSS와 마크업, 구현 세부정보 숨기기, 작성자 독립 실행 구성요소를 살펴보겠습니다.

소개

Shadow DOM은 세 가지 웹 구성요소 표준 중 하나입니다. HTML 템플릿, Shadow DOM맞춤 요소. HTML 가져오기 이전에는 목록의 일부였지만 이제 지원 중단되었습니다.

Shadow DOM을 사용하는 웹 구성 요소를 작성할 필요가 없습니다. 하지만 그렇게 할 때, 장점 (CSS 범위 지정, DOM 캡슐화, 컴포지션)을 빌드하고 재사용 가능한 커스텀 요소, 복원력이 우수하고, 구성이 용이하며, 재사용 가능성이 매우 높습니다. 커스텀인 경우 요소는 JS API를 사용하여 새 HTML을 만드는 방법이고 Shadow DOM은 제공할 수 있습니다. 두 API가 결합되어 HTML, CSS, JavaScript를 사용해 빌드할 수 있습니다.

Shadow DOM은 구성 요소 기반 앱을 빌드하기 위한 도구로 고안되었습니다. 따라서 웹 개발의 일반적인 문제에 대한 솔루션을 제공합니다.

  • 격리된 DOM: 구성요소의 DOM은 독립적입니다 (예: document.querySelector()는 구성요소의 Shadow DOM에 노드를 반환하지 않습니다.)
  • 범위가 지정된 CSS: Shadow DOM 내부에 정의된 CSS는 범위가 Shadow DOM으로 지정됩니다. 스타일 규칙 유출되지 않고 페이지 스타일이 스며들지 않습니다.
  • 컴포지션: 구성요소를 위한 선언적 마크업 기반 API를 설계합니다.
  • CSS 단순화 - 범위가 지정된 DOM을 사용하면 간단한 CSS 선택자를 사용할 수 있습니다. 일반적인 ID/클래스 이름을 만들고 이름 충돌을 걱정할 필요가 없습니다.
  • 생산성 - 큰 영역이 아닌 DOM 청크로 앱을 고려합니다. (global) 페이지를 참조하세요.
를 통해 개인정보처리방침을 정의할 수 있습니다.

fancy-tabs 데모

이 도움말에서는 데모 구성요소 (<fancy-tabs>)를 참조합니다. 거기에서 참조 코드 스니펫을 생성합니다. 브라우저에서 API를 지원하는 경우 바로 아래에서 라이브 데모를 볼 수 있습니다. 또는 GitHub의 전체 소스를 확인하세요.

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder"> <ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder"></ph> GitHub에서 소스 보기

Shadow DOM이란 무엇인가요?

DOM의 배경

HTML은 작업이 용이하기 때문에 웹을 강화합니다. 몇 개의 태그를 선언하면 프레젠테이션과 구조가 모두 포함된 페이지를 몇 초 만에 작성할 수 있습니다. 하지만 그 자체로는 HTML이 그다지 유용하지 않습니다. 인간은 텍스트를 이해하기 쉽습니다. 기계에는 더 많은 것이 필요합니다. 문서 객체 입력 모델, 즉 DOM을 사용합니다

브라우저는 웹페이지를 로드할 때 여러 가지 흥미로운 작업을 수행합니다. 다음 중 하나 작성자의 HTML을 라이브 문서로 변환하는 것입니다. 기본적으로 페이지의 구조를 이해하기 위해 브라우저는 HTML (정적 이미지)을 텍스트 문자열)을 데이터 모델 (객체/노드)으로 변환합니다. 브라우저는 HTML 계층 구조를 만들 수 있습니다. 멋진 점 DOM은 페이지를 실시간으로 표시한다는 것입니다. 정적인 우리가 작성하는 HTML로, 브라우저에서 생성한 노드에는 속성, 메서드 및 최상의 무엇보다도... 프로그램에서 조작할 수 있습니다! 그래서 DOM을 만들고 요소를 직접 사용할 수 있습니다.

const header = document.createElement('header');
const h1 = document.createElement('h1');
h1.textContent = 'Hello DOM';
header.appendChild(h1);
document.body.appendChild(header);

위 코드는 다음과 같은 HTML 마크업을 생성합니다.

<body>
    <header>
    <h1>Hello DOM</h1>
    </header>
</body>

모두 잘 하고 계시네요. 그런 다음 Shadow DOM이란 무엇인가요?

DOM... 섀도우

Shadow DOM은 일반 DOM이며 다음과 같은 두 가지 차이점이 있습니다. 1) 생성/사용 방법 및 2) 페이지의 나머지 부분과 비교하여 어떻게 작동하는지를 볼 수 있습니다. 일반적으로 DOM을 만들고 노드를 찾아 다른 요소의 하위 요소로 추가할 수 있습니다. Shadow DOM을 사용하면 요소에 연결되지만 해당 요소와는 별개인 범위가 지정된 DOM 트리 생성 확인할 수 있습니다. 이 범위가 지정된 하위 트리를 섀도 트리라고 합니다. 요소 섀도 호스트입니다. 그림자에 추가하는 모든 것은 <style>를 포함하여 호스팅 요소에 로컬로 적용됩니다. 이것이 Shadow DOM이 CSS 스타일 범위 지정을 실현합니다.

Shadow DOM 만들기

섀도 루트는 '호스트' 요소에 연결된 문서 프래그먼트입니다. 섀도우 루트를 연결하는 작업은 요소가 Shadow DOM을 얻는 방법입니다. 받는사람 요소의 Shadow DOM을 만들려면 element.attachShadow()를 호출합니다.

const header = document.createElement('header');
const shadowRoot = header.attachShadow({mode: 'open'});
shadowRoot.innerHTML = '<h1>Hello Shadow DOM</h1>'; // Could also use appendChild().

// header.shadowRoot === shadowRoot
// shadowRoot.host === header

.innerHTML를 사용하여 섀도우 루트를 채우지만 다른 DOM을 사용할 수도 있습니다. API에 액세스할 수 있습니다 이것이 바로 웹입니다. 우리는 선택권이 있습니다.

사양은 요소 목록을 정의합니다. 애플리케이션을 실행할 수 없습니다 여러 가지 이유로 요소가 있습니다.

  • 브라우저가 이미 요소에 대한 자체 내부 Shadow DOM을 호스팅함 (<textarea>, <input>)
  • 요소가 Shadow DOM (<img>)을 호스팅하는 것은 적절하지 않습니다.

예를 들어 다음은 작동하지 않습니다.

    document.createElement('input').attachShadow({mode: 'open'});
    // Error. `<input>` cannot host shadow dom.

사용자설정 요소에 대한 Shadow DOM 만들기

Shadow DOM은 Shadow DOM을 만들 때 커스텀 요소를 사용합니다. Shadow DOM을 사용하여 요소의 HTML, CSS 및 JS를 구획화하므로 '웹 구성 요소'를 생성합니다.

- 맞춤 요소가 Shadow DOM을 자체에 연결 DOM/CSS 캡슐화:

// Use custom elements API v1 to register a new HTML tag and define its JS behavior
// using an ES6 class. Every instance of <fancy-tab> will have this same prototype.
customElements.define('fancy-tabs', class extends HTMLElement {
    constructor() {
    super(); // always call super() first in the constructor.

    // Attach a shadow root to <fancy-tabs>.
    const shadowRoot = this.attachShadow({mode: 'open'});
    shadowRoot.innerHTML = `
        <style>#tabs { ... }</style> <!-- styles are scoped to fancy-tabs! -->
        <div id="tabs">...</div>
        <div id="panels">...</div>
    `;
    }
    ...
});

여기서 몇 가지 흥미로운 일이 진행되고 있습니다. 첫 번째는 <fancy-tabs> 인스턴스가 발생하면 맞춤 요소가 자체 Shadow DOM을 만듭니다. 생성됩니다. constructor()에서 하면 됩니다. 둘째로, 새로운 광고를 만들기 때문에 섀도 루트를 사용하면 <style> 내부의 CSS 규칙의 범위가 <fancy-tabs>로 지정됩니다.

컴포지션 및 슬롯

컴포지션은 잘 알려지지 않은 Shadow DOM 기능 중 하나이지만 가장 중요하다고 생각합니다

웹 개발의 세계에서 컴포지션은 앱을 구성하는 방법입니다. 명시적으로 정의할 수 있습니다 다양한 빌딩 블록 (<div>, <header>, <form>, <input>)가 함께 모여 앱을 형성합니다. 이러한 태그 중 일부는 영향을 미칠 수 있습니다 컴포지션은 <select>와 같은 네이티브 요소로 인해 <details>, <form>, <video>는 매우 유연합니다. 이러한 각 태그는 특정 HTML을 자식으로 처리하여 특별한 작업을 수행합니다. 예를 들어 <select><option><optgroup>를 드롭다운으로 렌더링하는 방법을 알고 있습니다. 다중 선택 위젯을 제공합니다. <details> 요소는 <summary>를 펼칠 수 있는 화살표입니다. <video>도 특정 자녀를 다루는 방법을 알고 있습니다. <source> 요소는 렌더링되지 않지만 동영상 동작에 영향을 미칩니다. 정말 놀랍군요!

용어: Light DOM과 Shadow DOM

Shadow DOM 컴퍼지션은 웹에서 여러 가지 새로운 기본 항목을 도입함 살펴봤습니다 본론으로 들어가기 전에 몇 가지 기본 사항을 표준화하고 동일한 용어를 사용합니다.

Light DOM

구성요소 사용자가 작성하는 마크업입니다. 이 DOM은 구성요소의 Shadow DOM을 사용합니다. 요소의 실제 하위 요소입니다.

<better-button>
    <!-- the image and span are better-button's light DOM -->
    <img src="gear.svg" slot="icon">
    <span>Settings</span>
</better-button>

Shadow DOM

구성 요소 작성자가 쓰는 DOM입니다. Shadow DOM은 구성 요소에 로컬이며 내부 구조, 범위가 지정된 CSS를 정의하며 구현을 캡슐화합니다. 확인하세요. 또한 소비자가 작성한 마크업을 렌더링하는 방법을 정의할 수도 있습니다. 확인할 수 있습니다.

#shadow-root
    <style>...</style>
    <slot name="icon"></slot>
    <span id="wrapper">
    <slot>Button</slot>
    </span>

평면화된 DOM 트리

브라우저가 사용자의 Light DOM을 섀도우에 배포한 결과 최종 제품을 렌더링합니다. 평면화된 트리는 페이지에 렌더링된 항목을 살펴봅니다.

<better-button>
    #shadow-root
    <style>...</style>
    <slot name="icon">
        <img src="gear.svg" slot="icon">
    </slot>
    <span id="wrapper">
        <slot>
        <span>Settings</span>
        </slot>
    </span>
</better-button>

<slot> 요소

Shadow DOM은 <slot> 요소를 사용하여 여러 DOM 트리를 함께 구성합니다. 슬롯은 사용자가 자신의 슬롯으로 채울 수 있는 구성요소 내부의 자리표시자입니다. 자체 마크업을 추가해야 합니다. 슬롯을 하나 이상 정의하여 외부 마크업을 초대하여 Shadow DOM에서 사용할 수 있습니다. 기본적으로 '사용자의 '여기에 마크업 추가'가 표시됩니다.

요소의 '교차' <slot>가 초대할 때 Shadow DOM 경계 삽입해야 합니다. 이러한 요소를 분산 노드라고 합니다. 개념적으로 분산 노드는 다소 기이하게 보일 수 있습니다. 슬롯은 DOM을 물리적으로 이동하지 않습니다. 그들은 Shadow DOM 내부의 다른 위치에서 렌더링합니다.

구성요소는 Shadow DOM에서 슬롯을 0개 이상 정의할 수 있습니다. 슬롯은 비워 둘 수 있음 대체 콘텐츠를 제공할 수 있습니다 사용자가 light DOM을 제공하지 않는 경우 슬롯이 대체 콘텐츠를 렌더링합니다.

<!-- Default slot. If there's more than one default slot, the first is used. -->
<slot></slot>

<slot>fallback content</slot> <!-- default slot with fallback content -->

<slot> <!-- default slot entire DOM tree as fallback -->
    <h2>Title</h2>
    <summary>Description text</summary>
</slot>

이름이 지정된 슬롯을 만들 수도 있습니다. 명명된 슬롯은 사용자가 이름으로 참조하는 shadow DOM입니다.

- <fancy-tabs>의 Shadow DOM의 슬롯:

#shadow-root
    <div id="tabs">
    <slot id="tabsSlot" name="title"></slot> <!-- named slot -->
    </div>
    <div id="panels">
    <slot id="panelsSlot"></slot>
    </div>

구성요소 사용자는 다음과 같이 <fancy-tabs>를 선언합니다.

<fancy-tabs>
    <button slot="title">Title</button>
    <button slot="title" selected>Title 2</button>
    <button slot="title">Title 3</button>
    <section>content panel 1</section>
    <section>content panel 2</section>
    <section>content panel 3</section>
</fancy-tabs>

<!-- Using <h2>'s and changing the ordering would also work! -->
<fancy-tabs>
    <h2 slot="title">Title</h2>
    <section>content panel 1</section>
    <h2 slot="title" selected>Title 2</h2>
    <section>content panel 2</section>
    <h2 slot="title">Title 3</h2>
    <section>content panel 3</section>
</fancy-tabs>

그리고 평면화된 트리의 모습은 다음과 같습니다.

<fancy-tabs>
    #shadow-root
    <div id="tabs">
        <slot id="tabsSlot" name="title">
        <button slot="title">Title</button>
        <button slot="title" selected>Title 2</button>
        <button slot="title">Title 3</button>
        </slot>
    </div>
    <div id="panels">
        <slot id="panelsSlot">
        <section>content panel 1</section>
        <section>content panel 2</section>
        <section>content panel 3</section>
        </slot>
    </div>
</fancy-tabs>

구성 요소가 다양한 구성을 처리할 수 있지만 평면화된 DOM 트리는 동일하게 유지됩니다. <button>에서 다음으로 전환할 수도 있습니다. <h2> 이 구성요소는 다양한 유형의 하위 요소를 처리하도록 제작되었습니다. <select>처럼 말이죠.

스타일 지정

웹 구성요소의 스타일을 지정하는 여러 가지 옵션이 있습니다. 그림자를 사용하는 구성요소 DOM은 기본 페이지에서 스타일을 지정하거나 자체 스타일을 정의하거나 후크를 제공할 수 있습니다( CSS 맞춤 속성 양식을 사용하여 기본값을 재정의할 수 있습니다.

구성요소 정의 스타일

Shadow DOM의 가장 유용한 기능은 범위가 지정된 CSS입니다.

  • 외부 페이지의 CSS 선택자는 구성요소 내부에 적용되지 않습니다.
  • 내부에 정의된 스타일은 스며 나오지 않습니다. 범위가 호스트 요소로 지정됩니다.

Shadow DOM 내에서 사용되는 CSS 선택자는 구성요소에 로컬로 적용됩니다. 포함 즉, 공통 ID/클래스 이름을 다시 사용할 수 있습니다. 확인할 수 있습니다 더 간단한 CSS 선택자를 위한 권장사항 Shadow DOM 내부에 넣습니다. 성능도 좋습니다.

- 섀도우 루트에 정의된 스타일은 로컬

#shadow-root
    <style>
    #panels {
        box-shadow: 0 2px 2px rgba(0, 0, 0, .3);
        background: white;
        ...
    }
    #tabs {
        display: inline-flex;
        ...
    }
    </style>
    <div id="tabs">
    ...
    </div>
    <div id="panels">
    ...
    </div>

스타일시트의 범위도 섀도우 트리로 지정됩니다.

#shadow-root
    <link rel="stylesheet" href="styles.css">
    <div id="tabs">
    ...
    </div>
    <div id="panels">
    ...
    </div>

<select> 요소가 다중 선택 위젯을 렌더링하는 방법 드롭다운)에서 다음과 같이 multiple 속성을 추가할 수 있습니다.

<select multiple>
  <option>Do</option>
  <option selected>Re</option>
  <option>Mi</option>
  <option>Fa</option>
  <option>So</option>
</select>

<select>는 내가 지정한 속성에 따라 자신의 스타일을 다르게 지정할 수 있습니다. 정의합니다. 웹 구성요소도 :host를 사용하여 자체 스타일을 지정할 수 있습니다. 선택기가 표시됩니다.

- 구성요소 스타일 지정 자체

<style>
:host {
    display: block; /* by default, custom elements are display: inline */
    contain: content; /* CSS containment FTW. */
}
</style>

:host의 한 가지 문제는 상위 페이지의 규칙이 더 높은 특정성을 가져야 한다는 것입니다. 지정된 :host 규칙보다 더 많은 규칙이 적용됩니다. 즉, 외부 스타일이 적용됩니다. 이 사용자가 외부에서 최상위 수준 스타일을 재정의할 수 있습니다. 기타: :host 섀도우 루트 컨텍스트에서만 작동하므로 shadow DOM을 사용할 수 있습니다.

:host(<selector>)의 기능적 형식을 사용하면 다음 경우에 호스트를 타겟팅할 수 있습니다. <selector>와 일치합니다. 이렇게 하면 구성 요소가 API를 캡슐화하고 사용자 상호작용이나 상태에 반응하거나 또는 이를 기반으로 내부 노드의 스타일을 지정하는 호스트에서 실행되어야 합니다.

<style>
:host {
    opacity: 0.4;
    will-change: opacity;
    transition: opacity 300ms ease-in-out;
}
:host(:hover) {
    opacity: 1;
}
:host([disabled]) { /* style when host has disabled attribute. */
    background: grey;
    pointer-events: none;
    opacity: 0.4;
}
:host(.blue) {
    color: blue; /* color host when it has class="blue" */
}
:host(.pink) > #tabs {
    color: pink; /* color internal #tabs node when host has class="pink". */
}
</style>

컨텍스트에 따른 스타일 지정

:host-context(<selector>)는 구성요소 또는 상위 구성요소가 하나라도 있는 경우 그 구성요소와 일치합니다. <selector>와 일치합니다. 이에 대한 일반적인 용도는 구성요소의 주변 환경입니다. 예를 들어 많은 사람들이 <html> 또는 <body>:

<body class="darktheme">
    <fancy-tabs>
    ...
    </fancy-tabs>
</body>

:host-context(.darktheme)는 하위 요소일 때 <fancy-tabs>의 스타일을 지정합니다. /.darktheme:

:host-context(.darktheme) {
    color: white;
    background: black;
}

:host-context()는 테마 설정에 유용할 수 있지만 CSS 맞춤 속성을 사용하여 후크 만들기

분산 노드 스타일 지정

::slotted(<compound-selector>)는 단일 포드의 노드와 일치하는 <slot>

이름 배지 구성요소를 만들었다고 가정해 보겠습니다.

<name-badge>
    <h2>Eric Bidelman</h2>
    <span class="title">
    Digital Jedi, <span class="company">Google</span>
    </span>
</name-badge>

구성요소의 Shadow DOM은 사용자의 <h2>.title 스타일을 지정할 수 있습니다.

<style>
::slotted(h2) {
    margin: 0;
    font-weight: 300;
    color: red;
}
::slotted(.title) {
    color: orange;
}
/* DOESN'T WORK (can only select top-level nodes).
::slotted(.company),
::slotted(.title .company) {
    text-transform: uppercase;
}
*/
</style>
<slot></slot>

앞서 언급했듯이 <slot>는 사용자의 Light DOM을 이동하지 않습니다. 날짜 노드가 <slot>에 배포되면 <slot>는 DOM을 렌더링하지만 노드는 물리적으로 그대로 있습니다 분산 전에 적용된 스타일은 적용되어야 합니다. 그러나 Light DOM은 분산되어 있는 경우에는 할 수 있습니다. 추가 스타일 (Shadow DOM이 정의한 스타일)을 사용합니다.

<fancy-tabs>의 또 다른 자세한 예는 다음과 같습니다.

const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
    <style>
    #panels {
        box-shadow: 0 2px 2px rgba(0, 0, 0, .3);
        background: white;
        border-radius: 3px;
        padding: 16px;
        height: 250px;
        overflow: auto;
    }
    #tabs {
        display: inline-flex;
        -webkit-user-select: none;
        user-select: none;
    }
    #tabsSlot::slotted(*) {
        font: 400 16px/22px 'Roboto';
        padding: 16px 8px;
        ...
    }
    #tabsSlot::slotted([aria-selected="true"]) {
        font-weight: 600;
        background: white;
        box-shadow: none;
    }
    #panelsSlot::slotted([aria-hidden="true"]) {
        display: none;
    }
    </style>
    <div id="tabs">
    <slot id="tabsSlot" name="title"></slot>
    </div>
    <div id="panels">
    <slot id="panelsSlot"></slot>
    </div>
`;

이 예에는 탭 제목에 대해 이름이 지정된 슬롯과 슬롯이 있습니다. 사용자가 탭을 선택하면 선택 항목이 굵게 표시됩니다. 패널을 표시합니다. 이는 CPU 사용률이 높은 분산 노드를 selected 속성 사용자설정 요소의 JS (여기에 표시되지 않음)는 다음을 추가합니다. 속성을 올바르게 업데이트합니다.

외부에서 구성요소 스타일 지정

외부에서 구성요소의 스타일을 지정하는 몇 가지 방법이 있습니다. 가장 쉬운 방법은 태그 이름을 선택기로 사용하는 방법입니다.

fancy-tabs {
    width: 500px;
    color: red; /* Note: inheritable CSS properties pierce the shadow DOM boundary. */
}
fancy-tabs:hover {
    box-shadow: 0 3px 3px #ccc;
}

외부 스타일이 항상 Shadow DOM에 정의된 스타일보다 우선합니다. 예를 들어 사용자가 선택기 fancy-tabs { width: 500px; }를 작성하면 이 선택기가 구성요소의 규칙: :host { width: 650px;}

지금까지는 구성요소 자체의 스타일 지정에 대해서만 알아봤습니다. 그런데 만약 구성요소 내부의 스타일을 지정하고 싶다면 이를 위해서는 CSS 맞춤설정이 필요합니다. 속성

CSS 커스텀 속성을 사용하여 후크 만들기

구성요소 작성자가 스타일 지정 후크를 제공하는 경우 사용자는 내부 스타일을 조정할 수 있습니다. CSS 커스텀 속성 사용 이 아이디어는 개념적으로 <slot> '스타일 자리표시자'를 만들어 사용자가 재정의할 수 있습니다.

- <fancy-tabs>를 사용하면 사용자가 배경 색상을 재정의할 수 있습니다.

<!-- main page -->
<style>
    fancy-tabs {
    margin-bottom: 32px;
    --fancy-tabs-bg: black;
    }
</style>
<fancy-tabs background>...</fancy-tabs>

Shadow DOM 내부:

:host([background]) {
    background: var(--fancy-tabs-bg, #9E9E9E);
    border-radius: 10px;
    padding: 10px;
}

이 경우 구성요소는 black를 배경 값으로 사용합니다. 사용자가 제공해야 합니다. 그렇지 않으면 기본값은 #9E9E9E입니다.

고급 주제

폐쇄형 섀도우 루트 만들기 (금지)

'closed'라는 또 다른 Shadow DOM 유형이 있습니다. 있습니다. 배포를 만들 때 JavaScript 외부에서 내부 DOM에 액세스할 수 없음 확인할 수 있습니다. 이는 <video>와 같은 네이티브 요소의 작동 방식과 유사합니다. 브라우저가 <video>의 Shadow DOM에 액세스할 수 없습니다. 폐쇄형 모드 섀도우 루트를 사용하여 구현합니다

- 닫힌 섀도우 트리 만들기

const div = document.createElement('div');
const shadowRoot = div.attachShadow({mode: 'closed'}); // close shadow tree
// div.shadowRoot === null
// shadowRoot.host === div

폐쇄형 모드의 영향을 받는 다른 API는 다음과 같습니다.

  • Element.assignedSlot / TextNode.assignedSlotnull 반환
  • Event.composedPath(): 그림자 내부의 요소와 연결된 이벤트의 경우 DOM, [] 반환
를 통해 개인정보처리방침을 정의할 수 있습니다.

웹 구성 요소를 절대로 만들지 말아야 하는 이유를 요약하자면 {mode: 'closed'}:

  1. 인공적인 보안 공격자가 모든 네트워크에 Element.prototype.attachShadow을(를) 도용합니다.

  2. 폐쇄형 모드는 맞춤 요소 코드가 자체 요소에 액세스하지 못하도록 합니다. Shadow DOM을 사용합니다. 완전 실패입니다. 대신 참조 데이터를 보관해야 합니다. 나중에 querySelector() 등을 사용할 수도 있습니다. 이것은 완전히 이는 폐쇄형 모드의 원래 목적을 무효화합니다.

        customElements.define('x-element', class extends HTMLElement {
        constructor() {
        super(); // always call super() first in the constructor.
        this._shadowRoot = this.attachShadow({mode: 'closed'});
        this._shadowRoot.innerHTML = '<div class="wrapper"></div>';
        }
        connectedCallback() {
        // When creating closed shadow trees, you'll need to stash the shadow root
        // for later if you want to use it again. Kinda pointless.
        const wrapper = this._shadowRoot.querySelector('.wrapper');
        }
        ...
    });
    
  3. 비공개 모드는 최종 사용자를 위한 구성요소의 유연성을 떨어뜨립니다. 사용자로서 웹 구성 요소를 빌드하는 경우 기능을 사용할 수 있습니다. 구성 옵션 사용자가 원하는 사용 사례 일반적인 내부 노드에 적절한 스타일 지정 후크를 포함하는 것을 잊은 경우입니다. 폐쇄형 모드에서는 사용자가 기본값을 재정의하고 조정할 방법이 없습니다. 스타일입니다. 구성 요소의 내부에 액세스할 수 있다는 것은 매우 유용합니다. 궁극적으로 사용자는 구성 요소를 분기하거나, 다른 구성 요소를 찾거나, 원하는 작업을 하지 않을 수 있습니다. :(

JS에서 슬롯 사용하기

Shadow DOM API는 슬롯 작업을 위한 유틸리티를 제공하고 노드라는 두 가지 리소스가 있습니다 이들은 사용자설정 요소를 작성할 때 편리합니다.

슬롯 변경 이벤트

slotchange 이벤트는 슬롯의 분산 노드가 변경될 때 발생합니다. 대상 사용자가 Light DOM에서 자식을 추가/제거하는 경우를 예로 들 수 있습니다.

const slot = this.shadowRoot.querySelector('#slot');
slot.addEventListener('slotchange', e => {
    console.log('light dom children changed!');
});

Light DOM에 대한 다른 유형의 변경사항을 모니터링하려면 MutationObserver 드림 를 사용할 수 있습니다.

슬롯에서 어떤 요소가 렌더링되고 있나요?

어떤 요소가 슬롯에 연결되어 있는지 알면 도움이 될 때가 있습니다. 전화걸기 slot.assignedNodes(): 슬롯이 렌더링 중인 요소를 찾습니다. 이 {flatten: true} 옵션은 슬롯의 대체 콘텐츠도 반환합니다 (노드가 없는 경우). 배포 중).

예를 들어 Shadow DOM이 다음과 같다고 가정해 보겠습니다.

<slot><b>fallback content</b></slot>
사용통화결과
<my-component>구성요소 텍스트</my-component> slot.assignedNodes(); [component text]
&lt;my-component&gt;&lt;/my-component&gt; slot.assignedNodes(); []
&lt;my-component&gt;&lt;/my-component&gt; slot.assignedNodes({flatten: true}); [<b>fallback content</b>]

요소는 어떤 슬롯에 할당됩니까?

반대 질문에 답할 수도 있습니다. element.assignedSlot 알림 확인할 수 있습니다.

Shadow DOM 이벤트 모델

이벤트가 Shadow DOM에서 발생하면 그 대상은 캡슐화 때문입니다 즉, 이벤트를 다시 타겟팅하여 인코더-디코더에 있는 내부 요소에서 비롯된 것처럼 shadow DOM을 사용할 수 있습니다. 일부 이벤트는 Shadow DOM에서 전파되지도 않습니다.

섀도우 경계를 교차하는 이벤트는 다음과 같습니다.

  • 포커스 이벤트: blur, focus, focusin, focusout
  • 마우스 이벤트: click, dblclick, mousedown, mouseenter, mousemove
  • 휠 이벤트: wheel
  • 입력 이벤트: beforeinput, input
  • 키보드 이벤트: keydown, keyup
  • 컴포지션 이벤트: compositionstart, compositionupdate, compositionend
  • DragEvent: dragstart, drag, dragend, drop

섀도우 트리가 열려 있는 경우 event.composedPath()를 호출하면 배열이 반환됩니다. 노드 수를 나타냅니다

맞춤 이벤트 사용하기

섀도우 트리의 내부 노드에서 실행되는 커스텀 DOM 이벤트는 섀도우 경계를 넘지 않도록 합니다. 단, composed: true 플래그:

// Inside <fancy-tab> custom element class definition:
selectTab() {
    const tabs = this.shadowRoot.querySelector('#tabs');
    tabs.dispatchEvent(new Event('tab-select', {bubbles: true, composed: true}));
}

composed: false (기본값)인 경우 소비자가 이벤트를 리슨할 수 없습니다. 찾을 수 있습니다

<fancy-tabs></fancy-tabs>
<script>
    const tabs = document.querySelector('fancy-tabs');
    tabs.addEventListener('tab-select', e => {
    // won't fire if `tab-select` wasn't created with `composed: true`.
    });
</script>

포커스 처리

Shadow DOM의 이벤트 모델에서 회수하면 실행되는 이벤트 shadow DOM 내부는 호스팅 요소에서 온 것처럼 보이도록 조정됩니다. 예를 들어 섀도 루트 내에서 <input>를 클릭한다고 가정해 보겠습니다.

<x-focus>
    #shadow-root
    <input type="text" placeholder="Input inside shadow dom">

focus 이벤트는 <input>가 아닌 <x-focus>에서 발생한 것처럼 표시됩니다. 마찬가지로 document.activeElement<x-focus>입니다. 섀도우 루트가 mode:'open' (폐쇄 모드 참고)로 만들어진 경우 액세스 가능한 내부 노드입니다.

document.activeElement.shadowRoot.activeElement // only works with open mode.

여러 수준의 Shadow DOM이 작동 중인 경우 (예: 다른 커스텀 요소)을 사용하려면 섀도우 루트를 재귀적으로 드릴링해야 합니다. activeElement를 찾습니다.

function deepActiveElement() {
    let a = document.activeElement;
    while (a && a.shadowRoot && a.shadowRoot.activeElement) {
    a = a.shadowRoot.activeElement;
    }
    return a;
}

포커스를 위한 또 다른 옵션은 delegatesFocus: true 옵션으로, 섀도우 트리 내 요소의 포커스 동작:

  • Shadow DOM 내부의 노드를 클릭했는데 해당 노드가 포커스 가능한 영역이 아닌 경우 첫 번째 포커스 가능 영역이 포커스됩니다
  • Shadow DOM 내부의 노드가 포커스를 받으면 :focus가 추가할 수 있습니다.

- delegatesFocus: true가 포커스 동작을 변경하는 방법

<style>
    :focus {
    outline: 2px solid red;
    }
</style>

<x-focus></x-focus>

<script>
customElements.define('x-focus', class extends HTMLElement {
    constructor() {
    super(); // always call super() first in the constructor.

    const root = this.attachShadow({mode: 'open', delegatesFocus: true});
    root.innerHTML = `
        <style>
        :host {
            display: flex;
            border: 1px dotted black;
            padding: 16px;
        }
        :focus {
            outline: 2px solid blue;
        }
        </style>
        <div>Clickable Shadow DOM text</div>
        <input type="text" placeholder="Input inside shadow dom">`;

    // Know the focused element inside shadow DOM:
    this.addEventListener('focus', function(e) {
        console.log('Active element (inside shadow dom):',
                    this.shadowRoot.activeElement);
    });
    }
});
</script>

결과

DelegatesFocus: true 동작

위는 <x-focus>에 포커스가 있을 때 (사용자 클릭, 탭으로 이동, focus() 등), "클릭 가능한 Shadow DOM 텍스트" 클릭되거나 내부 <input>에 포커스가 있습니다 (autofocus 포함).

delegatesFocus: false를 설정하면 다음과 같이 표시됩니다.

<ph type="x-smartling-placeholder">
</ph> DelegatesFocus: false이고 내부 입력에 포커스가 있음 <ph type="x-smartling-placeholder">
</ph> delegatesFocus: false 및 내부 <input>에 포커스가 있음
를 통해 개인정보처리방침을 정의할 수 있습니다.
를 통해 개인정보처리방침을 정의할 수 있습니다. <ph type="x-smartling-placeholder">
</ph> DelegatesFocus: false 및 x-focus
    포커스를 획득합니다 (예: tabindex=&#39;0&#39; 있음). <ph type="x-smartling-placeholder">
</ph> delegatesFocus: false<x-focus> 포커스를 획득합니다 (예: tabindex="0").
<ph type="x-smartling-placeholder">
</ph> DelegatesFocus: false 및 &#39;클릭 가능한 Shadow DOM 텍스트&#39; 은
    (또는 요소의 shadow DOM 내의 다른 빈 영역이 클릭됨)에 전달합니다. <ph type="x-smartling-placeholder">
</ph> delegatesFocus: false 및 '클릭 가능한 Shadow DOM 텍스트' 은 (또는 요소의 shadow DOM 내의 다른 빈 영역이 클릭됨)에 전달합니다.

도움말 및 유용한 정보

지난 몇 년 동안 웹 구성 요소 작성에 대해 조금 배웠습니다. 난 이러한 팁이 구성 요소 작성에 유용하다는 것을 알게 될 것입니다. 디버깅할 수 있습니다.

CSS 포함 사용

일반적으로 웹 구성요소의 레이아웃/스타일/페인트는 상당히 독립적입니다. 사용 성능의 :hostCSS 포함 승리:

<style>
:host {
    display: block;
    contain: content; /* Boom. CSS containment FTW. */
}
</style>

상속 가능한 스타일 재설정

상속 가능한 스타일 (background, color, font, line-height 등)은 Shadow DOM에서 상속할 수 있습니다. 즉, 레이어 캡션으로 Shadow DOM 경계를 관통 기본값입니다. 새 슬레이트로 시작하려면 all: initial;를 사용하여 그림자 경계를 교차할 때 초기 값으로 상속 가능한 스타일을 설정합니다.

<style>
    div {
    padding: 10px;
    background: red;
    font-size: 25px;
    text-transform: uppercase;
    color: white;
    }
</style>

<div>
    <p>I'm outside the element (big/white)</p>
    <my-element>Light DOM content is also affected.</my-element>
    <p>I'm outside the element (big/white)</p>
</div>

<script>
const el = document.querySelector('my-element');
el.attachShadow({mode: 'open'}).innerHTML = `
    <style>
    :host {
        all: initial; /* 1st rule so subsequent properties are reset. */
        display: block;
        background: white;
    }
    </style>
    <p>my-element: all CSS properties are reset to their
        initial value using <code>all: initial</code>.</p>
    <slot></slot>
`;
</script>

페이지에서 사용하는 모든 맞춤 요소 찾기

페이지에 사용된 맞춤 요소를 찾는 것이 유용할 때가 있습니다. 그렇게 하려면 페이지에서 사용된 모든 요소의 Shadow DOM을 재귀적으로 트래버스해야 합니다.

const allCustomElements = [];

function isCustomElement(el) {
    const isAttr = el.getAttribute('is');
    // Check for <super-button> and <button is="super-button">.
    return el.localName.includes('-') || isAttr && isAttr.includes('-');
}

function findAllCustomElements(nodes) {
    for (let i = 0, el; el = nodes[i]; ++i) {
    if (isCustomElement(el)) {
        allCustomElements.push(el);
    }
    // If the element has shadow DOM, dig deeper.
    if (el.shadowRoot) {
        findAllCustomElements(el.shadowRoot.querySelectorAll('*'));
    }
    }
}

findAllCustomElements(document.querySelectorAll('*'));

<template>에서 요소 만들기

.innerHTML를 사용하여 섀도우 루트를 채우는 대신 선언적 <template> 템플릿은 API의 구조를 선언하는 웹 구성요소입니다.

다음 예제를 참조하세요. '맞춤 요소: 재사용 가능한 웹 구성요소 빌드하기'를 참고하세요.

역사 및 브라우저 지원

지난 몇 년간 웹 구성 요소를 알아봤다면 Chrome 35 이상/Opera가 사용자를 위해 이전 버전의 Shadow DOM을 제공하고 있다는 점을 있습니다. Blink는 일부 사용자를 대상으로 두 버전을 계속 동시에 지원할 예정입니다. 있습니다. v0 사양은 섀도우 루트를 만드는 다른 방법을 제공했습니다. (v1의 element.attachShadow 대신 element.createShadowRoot) 호출 이전 메서드는 계속해서 v0 의미 체계를 사용하여 섀도우 루트를 만들므로 기존 v0은 깨지지 않을 것입니다.

이전 v0 사양에 관심이 있는 경우 HTML5rocks 도움말: 1, 2, 3. 또한 2024년 1분기부터 Shadow DOM v0과 v1 간의 차이점을 설명합니다.

브라우저 지원

Shadow DOM v1은 Chrome 53에서 제공됩니다 (상태). Opera 40, Safari 10 및 Firefox 63 에지 개발을 시작했습니다.

Shadow DOM의 기능을 감지하려면 attachShadow가 있는지 확인합니다.

const supportsShadowDOMV1 = !!HTMLElement.prototype.attachShadow;

폴리필

브라우저 지원이 널리 제공될 때까지 shadydomshadycss 폴리필은 v1을 제공합니다. 기능을 사용할 수 있습니다. Shady DOM은 Shadow DOM 및 shadycss 폴리필의 DOM 범위 지정을 모방합니다. CSS 맞춤 속성 및 네이티브 API가 제공하는 스타일 범위 지정

폴리필을 설치합니다.

bower install --save webcomponents/shadydom
bower install --save webcomponents/shadycss

폴리필 사용:

function loadScript(src) {
    return new Promise(function(resolve, reject) {
    const script = document.createElement('script');
    script.async = true;
    script.src = src;
    script.onload = resolve;
    script.onerror = reject;
    document.head.appendChild(script);
    });
}

// Lazy load the polyfill if necessary.
if (!supportsShadowDOMV1) {
    loadScript('/bower_components/shadydom/shadydom.min.js')
    .then(e => loadScript('/bower_components/shadycss/shadycss.min.js'))
    .then(e => {
        // Polyfills loaded.
    });
} else {
    // Native shadow dom v1 support. Go to go!
}

자세한 내용은 https://github.com/webcomponents/shadycss#usage를 참조하세요. 를 참조하세요.

결론

처음으로 적절한 CSS 범위 지정을 수행하는 API 프리미티브가 있습니다. DOM 범위를 지정하고, 진정한 컴포지션을 가집니다. 다른 웹 구성요소 API와 결합 사용자 지정 요소와 마찬가지로 Shadow DOM은 API를 완전히 캡슐화하여 작성하는 방법을 제공합니다. 해킹되거나 <iframe>와 같은 오래된 수하물을 사용하지 않고 구성요소를 직접 교체할 수 있습니다.

오해하지 마세요. Shadow DOM은 확실히 복잡한 짐승입니다! 하지만 짐승이야 도움이 될 수 있습니다 시간을 내서 사용해 보세요. 자세히 알아보고 질문하세요.

추가 자료

FAQ

현재 Shadow DOM v1을 사용할 수 있나요?

폴리필을 사용하면 가능합니다. 브라우저 지원을 참고하세요.

Shadow DOM은 어떤 보안 기능을 제공하나요?

Shadow DOM은 보안 기능이 아닙니다. CSS 범위 지정을 위한 가벼운 도구입니다. 구성 요소에서 DOM 트리를 숨깁니다. 진정한 보안 경계를 원하는 경우 <iframe>를 사용합니다.

웹 구성요소가 Shadow DOM을 사용해야 하나요?

아닙니다. Shadow DOM을 사용하는 웹 구성 요소를 만들 필요가 없습니다. 하지만 Shadow DOM을 사용하는 사용자 지정 요소를 작성하면 CSS 범위 지정, DOM 캡슐화, 컴포지션과 같은 기능을 활용할 수 있습니다.

개방형 섀도우 루트와 폐쇄형 섀도우 루트의 차이점은 무엇인가요?

폐쇄형 섀도우 루트를 참고하세요.