Shadow DOM 301

고급 개념 및 DOM API

이 도움말에서는 Shadow DOM으로 할 수 있는 놀라운 작업을 자세히 설명합니다. 이 섹션은 Shadow DOM 101Shadow DOM 201에서 논의된 개념을 기반으로 합니다.

여러 섀도우 루트 사용

파티를 주최하는 경우 모두가 같은 방에 있으면 답답해집니다. 여러 개의 회의실에 사용자 그룹을 분산할 수 있는 옵션을 사용하려 합니다. Shadow DOM을 호스팅하는 요소도 이를 실행할 수 있습니다. 즉, 한 번에 두 개 이상의 섀도우 루트를 호스팅할 수 있습니다.

호스트에 여러 섀도우 루트를 연결하려고 하면 어떻게 되는지 살펴보겠습니다.

<div id="example1">Light DOM</div>
<script>
  var container = document.querySelector('#example1');
  var root1 = container.createShadowRoot();
  var root2 = container.createShadowRoot();
  root1.innerHTML = '<div>Root 1 FTW</div>';
  root2.innerHTML = '<div>Root 2 FTW</div>';
</script>

이미 그림자 트리를 연결했음에도 불구하고 'Root 2 FTW'가 렌더링됩니다. 호스트에 마지막으로 추가된 그림 트리가 이기기 때문입니다. 렌더링과 관련하여 LIFO 스택입니다. DevTools를 살펴보면 이 동작을 확인할 수 있습니다.

렌더링 파티에 마지막 그림자만 초대되는 경우 여러 그림자를 사용하는 이유는 무엇인가요? 그림자 삽입 지점을 입력합니다.

그림자 삽입 지점

'쉐도우 삽입 지점' (<shadow>)은 자리표시자라는 점에서 일반 삽입 지점 (<content>)과 유사합니다. 그러나 호스트의 콘텐츠에 대한 자리표시자가 아니라 다른 섀도우 트리의 호스트입니다. Shadow DOM 인셉션입니다.

아시다시피, 문제의 원인을 파고들수록 더 복잡해집니다. 따라서 사양에서는 여러 개의 <shadow> 요소가 재생될 때 어떤 일이 발생하는지 매우 명확하게 설명합니다.

원래 예시로 돌아가 보면 첫 번째 그림자 root1가 초대 목록에서 누락되었습니다. <shadow> 삽입 지점을 추가하면 다시 표시됩니다.

<div id="example2">Light DOM</div>
<script>
var container = document.querySelector('#example2');
var root1 = container.createShadowRoot();
var root2 = container.createShadowRoot();
root1.innerHTML = '<div>Root 1 FTW</div><content></content>';
**root2.innerHTML = '<div>Root 2 FTW</div><shadow></shadow>';**
</script>

이 예에는 몇 가지 흥미로운 점이 있습니다.

  1. 'Root 2 FTW'가 여전히 'Root 1 FTW' 위에 렌더링됩니다. <shadow> 삽입 지점을 배치한 위치 때문입니다. 반대로 하려면 삽입 지점(root2.innerHTML = '<shadow></shadow><div>Root 2 FTW</div>';)을 이동합니다.
  2. 이제 root1에 <content> 삽입 지점이 있습니다. 이렇게 하면 텍스트 노드 'Light DOM'이 렌더링과 함께 실행됩니다.

<shadow>에서 렌더링되는 항목

<shadow>에서 렌더링되는 이전 그림자 트리를 아는 것이 유용할 때가 있습니다. .olderShadowRoot를 통해 이 트리에 대한 참조를 가져올 수 있습니다.

**root2.olderShadowRoot** === root1 //true

호스트의 그림자 루트 가져오기

요소가 Shadow DOM을 호스팅하는 경우 .shadowRoot를 사용하여 가장 어린 섀도우 루트에 액세스할 수 있습니다.

var root = host.createShadowRoot();
console.log(host.shadowRoot === root); // true
console.log(document.body.shadowRoot); // null

사용자가 그림자를 가로지르는 것을 방지하려면 .shadowRoot를 null로 재정의합니다.

Object.defineProperty(host, 'shadowRoot', {
  get: function() { return null; },
  set: function(value) { }
});

약간의 해킹이지만 작동합니다. 마지막으로, Shadow DOM은 놀라울 정도로 멋진 기능이지만 보안 기능으로 설계되지 않았습니다. 완전한 콘텐츠 격리를 위해 이 방법에 의존하지 마세요.

JS에서 Shadow DOM 빌드

JS에서 DOM을 빌드하는 것을 선호하는 경우 HTMLContentElementHTMLShadowElement에 이를 위한 인터페이스가 있습니다.

<div id="example3">
  <span>Light DOM</span>
</div>
<script>
var container = document.querySelector('#example3');
var root1 = container.createShadowRoot();
var root2 = container.createShadowRoot();

var div = document.createElement('div');
div.textContent = 'Root 1 FTW';
root1.appendChild(div);

 // HTMLContentElement
var content = document.createElement('content');
content.select = 'span'; // selects any spans the host node contains
root1.appendChild(content);

var div = document.createElement('div');
div.textContent = 'Root 2 FTW';
root2.appendChild(div);

// HTMLShadowElement
var shadow = document.createElement('shadow');
root2.appendChild(shadow);
</script>

이 예시는 이전 섹션의 예와 거의 동일합니다. 유일한 차이점은 이제 select를 사용하여 새로 추가된 <span>를 가져온다는 점입니다.

삽입 지점 작업

호스트 요소에서 선택되어 그림자 트리에 '배포'되는 노드를 드럼롤…분산 노드라고 합니다. 삽입점이 이를 초대할 때는 shadow 경계를 교차할 수 있습니다.

삽입 지점의 개념적으로 기이한 점은 DOM을 물리적으로 이동하지 않는다는 것입니다. 호스트의 노드는 그대로 유지됩니다. 삽입 지점은 노드를 호스트에서 그림자 트리로 다시 투사할 뿐입니다. 프레젠테이션/렌더링 관련 내용입니다. '이 노드를 여기로 이동' '이 위치에 이 노드를 렌더링'

예를 들면 다음과 같습니다.

<div><h2>Light DOM</h2></div>
<script>
var root = document.querySelector('div').createShadowRoot();
root.innerHTML = '<content select="h2"></content>';

var h2 = document.querySelector('h2');
console.log(root.querySelector('content[select="h2"] h2')); // null;
console.log(root.querySelector('content').contains(h2)); // false
</script>

그러면 h2가 shadow DOM의 하위 요소가 아닙니다. 여기서 또 하나의 팁을 알려드리겠습니다.

Element.getDistributedNodes()

<content>를 탐색할 수는 없지만 .getDistributedNodes() API를 사용하면 삽입 지점에서 분산 노드를 쿼리할 수 있습니다.

<div id="example4">
  <h2>Eric</h2>
  <h2>Bidelman</h2>
  <div>Digital Jedi</div>
  <h4>footer text</h4>
</div>

<template id="sdom">
  <header>
    <content select="h2"></content>
  </header>
  <section>
    <content select="div"></content>
  </section>
  <footer>
    <content select="h4:first-of-type"></content>
  </footer>
</template>

<script>
var container = document.querySelector('#example4');

var root = container.createShadowRoot();

var t = document.querySelector('#sdom');
var clone = document.importNode(t.content, true);
root.appendChild(clone);

var html = [];
[].forEach.call(root.querySelectorAll('content'), function(el) {
  html.push(el.outerHTML + ': ');
  var nodes = el.getDistributedNodes();
  [].forEach.call(nodes, function(node) {
    html.push(node.outerHTML);
  });
  html.push('\n');
});
</script>

Element.getDestinationInsertionPoints()

.getDistributedNodes()와 마찬가지로 .getDestinationInsertionPoints()를 호출하여 노드가 배포되는 삽입 지점을 확인할 수 있습니다.

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

<script>
  var container = document.querySelector('div');

  var root1 = container.createShadowRoot();
  var root2 = container.createShadowRoot();
  root1.innerHTML = '<content select="h2"></content>';
  root2.innerHTML = '<shadow></shadow>';

  var h2 = document.querySelector('#host h2');
  var insertionPoints = h2.getDestinationInsertionPoints();
  [].forEach.call(insertionPoints, function(contentEl) {
    console.log(contentEl);
  });
</script>

도구: Shadow DOM 시각화 도구

Shadow DOM이라는 블랙 매직을 이해하는 것은 어렵습니다. 처음으로 이를 이해하려고 애썼던 기억이 납니다.

Shadow DOM 렌더링의 작동 방식을 시각화할 수 있도록 d3.js를 사용하여 도구를 빌드했습니다. 왼쪽의 두 마크업 상자는 모두 수정할 수 있습니다. 원하는 마크업을 붙여넣고 어떻게 작동하는지, 삽입 지점이 호스트 노드를 그림자 트리로 전환하는지 확인해 보세요.

Shadow DOM 비주얼라이저
Shadow DOM 시각화 도구 실행

사용해 보고 의견을 알려주세요.

이벤트 모델

일부 이벤트는 섀도우 경계를 교차하고 일부는 그렇지 않습니다. 이벤트가 경계를 넘는 경우 섀도우 루트의 상위 경계가 제공하는 캡슐화를 유지하기 위해 이벤트 타겟이 조정됩니다. 즉, 이벤트가 Shadow DOM의 내부 요소가 아닌 호스트 요소에서 온 것처럼 보이도록 다시 타겟팅됩니다.

Play 액션 1

  • 흥미로운 내용입니다. 호스트 요소 (<div data-host>)에서 파란색 노드로 연결되는 mouseout가 표시됩니다. 분산 노드이지만 ShadowDOM이 아닌 호스트에 있습니다. 마우스를 노란색으로 더 아래로 가져가면 파란색 노드에 mouseout가 표시됩니다.

Play 액션 2

  • 호스트에 mouseout가 하나 표시됩니다 (맨 끝). 일반적으로 모든 노란색 블록에 대해 mouseout 이벤트가 트리거됩니다. 그러나 이 경우 이러한 요소는 Shadow DOM 내부이며 이벤트가 상위 경계를 통해 버블링되지 않습니다.

플레이 액션 3

  • 입력을 클릭하면 focusin가 입력에 표시되지 않고 호스트 노드 자체에 표시됩니다. 재타겟팅되었습니다.

항상 중지되는 이벤트

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

  • 취소
  • 오류
  • select
  • 변경
  • load
  • 재설정
  • resize
  • scroll
  • selectstart

결론

Shadow DOM이 매우 강력하다는 데 동의해 주시기 바랍니다. 이제 <iframe> 또는 기타 이전 기법의 추가 부담 없이 적절한 캡슐화를 사용할 수 있습니다.

Shadow DOM은 확실히 복잡한 짐승이지만 웹 플랫폼에 추가할 가치가 있는 짐승입니다. 시간을 투자하여 알아보세요. 질문하기.

자세한 내용은 도미닉의 소개 도움말 Shadow DOM 101 및 제 도움말 Shadow DOM 201: CSS 및 스타일 지정을 참고하세요.