템플릿, 슬롯, 섀도우

웹 구성요소의 이점은 UI 위젯을 한 번 만들고 여러 번 재사용할 수 있다는 재사용성이 있습니다. 웹 구성요소를 만들려면 자바스크립트가 필요하지만 자바스크립트 라이브러리는 필요하지 않습니다. HTML과 관련 API가 필요한 모든 것을 제공합니다.

웹 구성요소 표준은 HTML 템플릿, 맞춤 요소Shadow DOM의 세 부분으로 구성됩니다. 이러한 요소를 함께 사용하면 이미 다룬 다른 모든 HTML 요소와 마찬가지로 기존 애플리케이션에 원활하게 통합할 수 있는 맞춤설정되고 독립 실행형 (캡슐화됨), 재사용 가능한 요소를 빌드할 수 있습니다.

이 섹션에서는 사용자가 1~5개의 별표 척도로 환경을 평가할 수 있는 웹 구성요소인 <star-rating> 요소를 만듭니다. 맞춤 요소의 이름을 지정할 때는 모두 소문자를 사용하는 것이 좋습니다. 또한 대시를 포함하면 일반 요소와 맞춤 요소를 구별하는 데 도움이 됩니다.

<template><slot> 요소, slot 속성, 자바스크립트를 사용하여 캡슐화된 Shadow DOM이 포함된 템플릿을 만드는 방법을 알아봅니다. 그런 다음 요소나 웹 구성요소와 마찬가지로 정의된 요소를 재사용하여 텍스트 섹션을 맞춤설정합니다. 또한 사용자설정 요소 내부와 외부에서 CSS를 사용하는 방법에 대해서도 간략하게 설명합니다.

<template> 요소

<template> 요소는 복제하여 JavaScript로 DOM에 삽입할 HTML의 프래그먼트를 선언하는 데 사용됩니다. 요소의 콘텐츠는 기본적으로 렌더링되지 않습니다. JavaScript를 사용하여 인스턴스화됩니다.

<template id="star-rating-template">
  <form>
    <fieldset>
      <legend>Rate your experience:</legend>
      <rating>
        <input type="radio" name="rating" value="1" aria-label="1 star" required />
        <input type="radio" name="rating" value="2" aria-label="2 stars" />
        <input type="radio" name="rating" value="3" aria-label="3 stars" />
        <input type="radio" name="rating" value="4" aria-label="4 stars" />
        <input type="radio" name="rating" value="5" aria-label="5 stars" />
      </rating>
    </fieldset>
    <button type="reset">Reset</button>
    <button type="submit">Submit</button>
  </form>
</template>

<template> 요소의 콘텐츠가 화면에 기록되지 않으므로 <form>와 그 콘텐츠는 렌더링되지 않습니다. 예. 이 Codepen은 비어 있지만 HTML 탭을 살펴보면 <template> 마크업을 볼 수 있습니다.

이 예에서 <form>은 DOM에서 <template>의 하위 요소가 아닙니다. 오히려 <template> 요소의 콘텐츠는 HTMLTemplateElement.content 속성에서 반환된 DocumentFragment의 하위 요소입니다. 표시되도록 하려면 자바스크립트를 사용하여 콘텐츠를 가져오고 해당 콘텐츠를 DOM에 추가해야 합니다.

이 간단한 자바스크립트는 맞춤 요소를 만들지 않았습니다. 대신 이 예에서는 <template>의 콘텐츠를 <body>에 추가했습니다. 콘텐츠가 표시 및 스타일을 지정할 수 있는 DOM의 일부가 되었습니다.

DOM에 표시된 이전 코드펜의 스크린샷

JavaScript에 별점 1개의 템플릿을 구현하도록 요구하는 것은 그다지 유용하지 않지만 반복적으로 사용되는 맞춤설정 가능한 별표 평점 위젯을 위한 웹 구성요소를 만드는 것은 유용합니다.

<slot> 요소

일치하는 항목별로 맞춤설정된 범례를 포함하는 슬롯이 포함되었습니다. HTML은 <slot> 요소를 <template> 내의 자리표시자로 제공합니다. 이름이 지정된 경우 '이름이 지정된 슬롯'을 만듭니다. 이름이 지정된 슬롯을 사용하면 웹 구성요소 내의 콘텐츠를 맞춤설정할 수 있습니다. <slot> 요소를 사용하면 맞춤 요소의 하위 요소를 섀도우 트리 내에 삽입해야 하는 위치를 제어할 수 있습니다.

템플릿에서 <legend><slot>로 변경합니다.

<template id="star-rating-template">
  <form>
    <fieldset>
      <slot name="star-rating-legend">
        <legend>Rate your experience:</legend>
      </slot>

name 속성은 요소에 값이 이름이 지정된 슬롯의 이름과 일치하는 slot 속성이 있는 경우 다른 요소에 슬롯을 할당하는 데 사용됩니다. 맞춤 요소에 슬롯에 일치하는 항목이 없으면 <slot>의 콘텐츠가 렌더링됩니다. 따라서 HTML에 콘텐츠 없이 <star-rating></star-rating>를 간단히 포함하는 경우 렌더링해도 괜찮은 일반 콘텐츠가 포함된 <legend>를 포함했습니다.

<star-rating>
  <legend slot="star-rating-legend">Blendan Smooth</legend>
</star-rating>
<star-rating>
  <legend slot="star-rating-legend">Hoover Sukhdeep</legend>
</star-rating>
<star-rating>
  <legend slot="star-rating-legend">Toasty McToastface</legend>
  <p>Is this text visible?</p>
</star-rating>

slot 속성은 <template> 내에서 <slot>의 콘텐츠를 대체하는 데 사용되는 전역 속성입니다. 맞춤 요소에서 슬롯 속성이 있는 요소는 <legend>입니다. 그럴 필요는 없습니다. 이 템플릿에서 <slot name="star-rating-legend"><anyElement slot="star-rating-legend">로 대체됩니다. 여기서 <anyElement>는 다른 맞춤 요소를 비롯한 임의의 요소가 될 수 있습니다.

정의되지 않은 요소

<template>에서는 <rating> 요소를 사용했습니다. 맞춤 요소가 아닙니다. 알 수 없는 요소입니다. 브라우저는 요소를 인식하지 못해도 실패하지 않습니다. 인식할 수 없는 HTML 요소는 브라우저에서 CSS로 스타일을 지정할 수 있는 익명 인라인 요소로 처리됩니다. <span>와 마찬가지로 <rating><star-rating> 요소에는 사용자 에이전트가 적용한 스타일이나 시맨틱이 없습니다.

<template>와 콘텐츠는 렌더링되지 않습니다. <template>는 렌더링되지 않는 콘텐츠가 포함된 알려진 요소입니다. <star-rating> 요소는 아직 정의되지 않았습니다. 요소를 정의할 때까지 브라우저는 요소를 인식할 수 없는 모든 요소와 동일하게 표시합니다. 지금은 인식할 수 없는 <star-rating>가 익명의 인라인 요소로 취급되므로 범례와 세 번째 <star-rating><p>가 포함된 콘텐츠는 대신 <span>에 있을 때와 같이 표시됩니다.

인식할 수 없는 이 요소를 맞춤 요소로 변환하도록 요소를 정의해 보겠습니다.

맞춤 요소

맞춤 요소를 정의하려면 자바스크립트가 필요합니다. 정의된 경우 <star-rating> 요소의 콘텐츠는 연결된 템플릿의 모든 콘텐츠가 포함된 섀도 루트로 대체됩니다. 템플릿의 <slot> 요소는 slot 속성 값이 <slot>의 이름 값과 일치하는 <star-rating> 내 요소의 내용(있는 경우)으로 대체됩니다. 그렇지 않으면 템플릿 슬롯의 콘텐츠가 표시됩니다.

슬롯과 연결되지 않은 맞춤 요소(세 번째 <star-rating><p>Is this text visible?</p>) 내의 콘텐츠는 섀도우 루트에 포함되지 않으므로 표시되지 않습니다.

HTMLElement를 확장하여 star-rating라는 맞춤 요소를 정의합니다.

customElements.define('star-rating',
  class extends HTMLElement {
    constructor() {
      super(); // Always call super first in constructor
      const starRating = document.getElementById('star-rating-template').content;
      const shadowRoot = this.attachShadow({
        mode: 'open'
      });
      shadowRoot.appendChild(starRating.cloneNode(true));
    }
  });

이제 요소가 정의되었으므로 브라우저에서 <star-rating> 요소를 만날 때마다 템플릿인 #star-rating-template가 있는 요소가 정의한 대로 렌더링됩니다. 브라우저는 Shadow DOM 트리를 노드에 연결하고 템플릿 콘텐츠의 클론을 Shadow DOM에 추가합니다. attachShadow()할 수 있는 요소는 제한됩니다.

const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(starRating.cloneNode(true));

개발자 도구를 살펴보면 <template><form>가 각 맞춤 요소의 섀도 루트의 일부임을 알 수 있습니다. <template> 콘텐츠의 클론은 개발자 도구의 각 맞춤 요소에서 명확하게 표시되고 브라우저에도 표시되지만, 맞춤 요소 자체의 콘텐츠는 화면에 렌더링되지 않습니다.

각 맞춤 요소에 클론된 템플릿 콘텐츠를 보여주는 DevTools 스크린샷

<template> 예에서는 템플릿 콘텐츠를 문서 본문에 추가하여 일반 DOM에 콘텐츠를 추가했습니다. customElements 정의에서 동일한 appendChild()를 사용했지만 클론된 템플릿 콘텐츠가 캡슐화된 Shadow DOM에 추가되었습니다.

별표가 스타일이 지정되지 않은 라디오 버튼으로 되돌아간 것을 확인하셨나요? 표준 DOM이 아닌 Shadow DOM의 일부이므로 Codepen의 CSS 탭 내에서 스타일 지정은 적용되지 않습니다. 이 탭의 CSS 스타일은 Shadow DOM이 아닌 문서로 범위가 지정되므로 스타일이 적용되지 않습니다. 캡슐화된 Shadow DOM 콘텐츠의 스타일을 지정하려면 캡슐화된 스타일을 만들어야 합니다.

Shadow DOM

Shadow DOM은 CSS 스타일의 범위를 각 섀도우 트리로 지정하여 문서의 나머지 부분과 분리합니다. 즉, 외부 CSS는 구성요소에 적용되지 않으며 구성요소 스타일은 의도적으로 지시하지 않는 한 문서의 나머지 부분에 영향을 미치지 않습니다.

Shadow DOM에 콘텐츠를 추가했으므로 맞춤 요소에 캡슐화된 CSS를 제공하는 <style> 요소를 포함할 수 있습니다.

맞춤 요소로 범위가 지정되면 스타일이 문서의 나머지 부분에 스며드는 것을 걱정할 필요가 없습니다. 선택기의 특이성을 크게 줄일 수 있습니다. 예를 들어, 맞춤 요소에 사용되는 유일한 입력이 라디오 버튼이므로 input[type="radio"] 대신 input를 선택기로 사용할 수 있습니다.

 <template id="star-rating-template">
  <style>
    rating {
      display: inline-flex;
    }
    input {
      appearance: none;
      margin: 0;
      box-shadow: none;
    }
    input::after {
      content: '\2605'; /* solid star */
      font-size: 32px;
    }
    rating:hover input:invalid::after,
    rating:focus-within input:invalid::after {
      color: #888;
    }
    input:invalid::after,
      rating:hover input:hover ~ input:invalid::after,
      input:focus ~ input:invalid::after  {
      color: #ddd;
    }
    input:valid {
      color: orange;
    }
    input:checked ~ input:not(:checked)::after {
      color: #ccc;
      content: '\2606'; /* hollow star */
    }
  </style>
  <form>
    <fieldset>
      <slot name="star-rating-legend">
        <legend>Rate your experience:</legend>
      </slot>
      <rating>
        <input type="radio" name="rating" value="1" aria-label="1 star" required/>
        <input type="radio" name="rating" value="2" aria-label="2 stars"/>
        <input type="radio" name="rating" value="3" aria-label="3 stars"/>
        <input type="radio" name="rating" value="4" aria-label="4 stars"/>
        <input type="radio" name="rating" value="5" aria-label="5 stars"/>
      </rating>
    </fieldset>
    <button type="reset">Reset</button>
    <button type="submit">Submit</button>
  </form>
</template>

웹 구성요소는 <template> 내 마크업으로 캡슐화되고 CSS 스타일은 Shadow DOM으로 범위가 지정되고 구성요소 외부의 모든 항목에서 숨겨지지만 렌더링되는 슬롯 콘텐츠(<star-rating><anyElement slot="star-rating-legend"> 부분)는 캡슐화되지 않습니다.

현재 범위를 벗어난 스타일 지정

Shadow DOM 내에서 문서의 스타일을 지정하고 전역 스타일에서 Shadow DOM 콘텐츠의 스타일을 지정하는 것이 가능하지만 단순하지는 않습니다. Shadow DOM이 끝나고 일반 DOM이 시작되는 그림자 경계를 순회할 수 있지만 순회할 수는 없습니다.

섀도우 트리는 Shadow DOM 내의 DOM 트리입니다. 섀도우 루트는 섀도우 트리의 루트 노드입니다.

:host 의사 클래스는 섀도우 호스트 요소인 <star-rating>를 선택합니다. 섀도우 호스트는 Shadow DOM이 연결된 DOM 노드입니다. 특정 버전의 호스트만 타겟팅하려면 :host()를 사용합니다. 그러면 전달된 매개변수와 일치하는 섀도우 호스트 요소(예: 클래스 또는 속성 선택기)만 선택됩니다. 모든 맞춤 요소를 선택하려면 전역 CSS에서 star-rating { /* styles */ }을 사용하거나 템플릿 스타일의 :host(:not(#nonExistantId))을 사용하면 됩니다. 특수성은 전역 CSS가 우선합니다.

::slotted() 유사 요소는 Shadow DOM 내에서 Shadow DOM 경계를 교차합니다. 선택기와 일치하는 경우 슬롯이 있는 요소를 선택합니다. 이 예에서 ::slotted(legend)는 세 개의 범례와 일치합니다.

전역 범위의 CSS에서 Shadow DOM을 타겟팅하려면 템플릿을 수정해야 합니다. part 속성은 스타일을 지정할 모든 요소에 추가할 수 있습니다. 그런 다음 ::part() 유사 요소를 사용하여 전달된 매개변수와 일치하는 섀도우 트리 내의 요소를 일치시킵니다. 유사 요소의 앵커 또는 시작 요소는 호스트 또는 맞춤 요소 이름(이 경우 star-rating)입니다. 매개변수는 part 속성의 값입니다.

템플릿 마크업이 다음과 같이 시작된 경우:

<template id="star-rating-template">
  <form part="formPart">
    <fieldset part="fieldsetPart">

다음을 사용하여 <form><fieldset>를 타겟팅할 수 있습니다.

star-rating::part(formPart) { /* styles */ }
star-rating::part(fieldsetPart) { /* styles */ }

부분 이름은 클래스와 유사하게 작동합니다. 요소는 공백으로 구분된 여러 부분 이름을 가질 수 있고 여러 요소가 동일한 부분 이름을 가질 수 있습니다.

Google에는 맞춤 요소를 만들기 위한 훌륭한 체크리스트가 있습니다. 선언적 Shadow DOM에 대해서도 알아보세요.

학습 내용 확인하기

템플릿, 슬롯, 그림자에 대한 지식을 테스트합니다.

기본적으로 Shadow DOM 외부의 스타일은 내부 요소의 스타일을 지정합니다.

참입니다.
다시 시도해 주세요.
거짓입니다.
정답입니다.

<template> 요소에 관한 올바른 설명은 무엇인가요?

페이지의 콘텐츠를 표시하는 데 사용되는 일반 요소입니다.
다시 시도해 주세요.
자리표시자 요소입니다.
다시 시도해 주세요.
HTML의 조각을 선언하는 데 사용되는 요소로, 기본적으로 렌더링되지 않습니다.
정답입니다.