JavaScript의 섀도우 DOM
이 글에서는 JavaScript의 섀도우 DOM에 대해 설명합니다.
YouTube Video
javascript-html-shadow-dom.html
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>JavaScript & HTML</title>
6 <style>
7 * {
8 box-sizing: border-box;
9 }
10
11 body {
12 margin: 0;
13 padding: 1em;
14 padding-bottom: 10em;
15 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16 background-color: #f7f9fc;
17 color: #333;
18 line-height: 1.6;
19 }
20
21 .container {
22 max-width: 800px;
23 margin: 0 auto;
24 padding: 1em;
25 background-color: #ffffff;
26 border: 1px solid #ccc;
27 border-radius: 10px;
28 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
29 }
30
31 h1, h2 {
32 font-size: 1.2rem;
33 color: #007bff;
34 margin-top: 0.5em;
35 margin-bottom: 0.5em;
36 border-left: 5px solid #007bff;
37 padding-left: 0.6em;
38 background-color: #e9f2ff;
39 }
40
41 button {
42 display: block;
43 margin: 1em auto;
44 padding: 0.75em 1.5em;
45 font-size: 1rem;
46 background-color: #007bff;
47 color: white;
48 border: none;
49 border-radius: 6px;
50 cursor: pointer;
51 transition: background-color 0.3s ease;
52 }
53
54 button:hover {
55 background-color: #0056b3;
56 }
57
58 #output {
59 margin-top: 1em;
60 background-color: #1e1e1e;
61 color: #0f0;
62 padding: 1em;
63 border-radius: 8px;
64 min-height: 200px;
65 font-family: Consolas, monospace;
66 font-size: 0.95rem;
67 overflow-y: auto;
68 white-space: pre-wrap;
69 }
70
71 .highlight {
72 outline: 3px solid #ffc107; /* yellow border */
73 background-color: #fff8e1; /* soft yellow background */
74 transition: background-color 0.3s ease, outline 0.3s ease;
75 }
76
77 .active {
78 background-color: #28a745; /* green background */
79 color: #fff;
80 box-shadow: 0 0 10px rgba(40, 167, 69, 0.5);
81 transition: background-color 0.3s ease, box-shadow 0.3s ease;
82 }
83 </style>
84</head>
85<body>
86 <div class="container">
87 <h2>HTML Sample</h2>
88 <div id="content"></div>
89 <div id="shadow-host">Shadow Root Element</div>
90 <my-card></my-card>
91 </div>
92
93 <div class="container">
94 <h1>JavaScript Console</h1>
95 <button id="executeBtn">Execute</button>
96 <div id="output"></div>
97 </div>
98
99 <div>
100 <h2>Slot Sample</h2>
101 <my-element>
102 <h3 slot="header">Header Content</h1>
103 <p slot="content">Main Content</p>
104 </my-element>
105 </div>
106
107 <script>
108 // Override console.log to display messages in the #output element
109 (function () {
110 const originalLog = console.log;
111 console.log = function (...args) {
112 originalLog.apply(console, args);
113 const output = document.getElementById('output');
114 output.textContent += args.map(String).join(' ') + '\n';
115 };
116 })();
117
118 document.getElementById('executeBtn').addEventListener('click', () => {
119 // Prevent multiple loads
120 if (document.getElementById('externalScript')) return;
121
122 const script = document.createElement('script');
123 script.src = 'javascript-html-shadow-dom.js';
124 script.id = 'externalScript';
125 //script.onload = () => console.log('javascript-html-shadow-dom.js loaded and executed.');
126 //script.onerror = () => console.log('Failed to load javascript-html-shadow-dom.js.');
127 document.body.appendChild(script);
128 });
129 </script>
130</body>
131</html>
쉐도우 DOM
이해하기
쉐도우 DOM
은 웹 컴포넌트 표준의 강력한 기능으로, 컴포넌트 내에서 스타일과 DOM 구조를 캡슐화할 수 있도록 합니다. 이 기능은 컴포넌트와 메인 문서 간의 스타일 및 스크립트 간섭을 방지합니다.
JavaScript의 섀도우 DOM
쉐도우 DOM
은 일반 DOM 요소와 연결된 스코프 DOM 트리를 만드는 방법을 제공합니다. 이 쉐도우 트리는 전체 문서에서 격리되어 외부 스타일과 스크립트가 영향을 미치지 않으며, 내부 스타일 및 스크립트도 외부로 누출되지 않습니다.
예를 들어, 쉐도우 DOM
을 사용하여 사용자 정의 버튼 컴포넌트를 만들면 해당 스타일이 페이지의 다른 요소에 영향을 미치지 않습니다. 마찬가지로 동일한 클래스 이름을 가진 요소들도 충돌하지 않습니다.
섀도우 DOM
외부의 일반 HTML 콘텐츠를 **라이트 DOM
**이라고 부릅니다.
쉐도우 DOM
의 장점
-
캡슐화
섀도우 DOM
은 스타일과 기능을 분리하여 전역 스타일 및 스크립트와의 충돌을 방지합니다.
-
재사용성
섀도우 DOM
으로 구성된 컴포넌트는 스타일 충돌 걱정 없이 여러 프로젝트에서 재사용할 수 있습니다.
-
유지보수성
- 캡슐화 덕분에 컴포넌트의 로직과 스타일이 독립적으로 관리되어, 디버깅과 유지보수가 쉬워집니다.
쉐도우 DOM
생성하기
쉐도우 DOM
을 사용하려면 HTML 요소에 쉐도우 루트를 첨부해야 합니다. 간단한 예시는 다음과 같습니다:.
1// Select the host element
2const hostElement = document.querySelector('#shadow-host');
3
4// Attach a shadow root
5const shadowRoot = hostElement.attachShadow({ mode: 'open' });
설명
이 코드는 다음과 같은 요소를 포함합니다:.
-
호스트 요소
- 섀도우 루트가 연결되는 일반 DOM 요소입니다 (여기서는
#shadow-host
).
- 섀도우 루트가 연결되는 일반 DOM 요소입니다 (여기서는
-
섀도우 루트
attachShadow
로 생성된 섀도우 트리의 루트입니다.
-
모드
open
모드에서는 외부 JavaScript가element.shadowRoot
를 통해 섀도우 루트에 접근할 수 있습니다. 반면에,closed
모드는 접근을 허용하지 않습니다.
쉐도우 DOM
내에서 스타일링하기
Shadow DOM
은 자체 스타일 범위를 가지고 있습니다. Shadow 트리 내에서 정의된 스타일은 해당 트리 내의 요소에만 적용됩니다. 다음은 예제입니다:.
1// Add content to the shadow root
2shadowRoot.innerHTML = `
3 <style>
4 p {
5 color: green;
6 }
7 </style>
8 <p>Scoped style inside Shadow DOM.</p>
9`;
메인 문서에 충돌하는 스타일이 있더라도, 섀도우 트리 내부의 단락에는 영향을 주지 않습니다.
1const content = document.getElementById('content');
2content.innerHTML = `
3 <style>
4 p {
5 color: red;
6 }
7 </style>
8 <p>This is in the main DOM.</p>
9`;
섀도우 DOM
내의 단락은 초록색을 유지하며, 외부 단락은 빨간색이 됩니다.
Shadow DOM
내에서의 이벤트
Shadow DOM
내의 이벤트는 일반 DOM 이벤트와 유사하지만 캡슐화로 인해 전파 방식이 다를 수 있습니다.
다음은 예제입니다:.
1// Add an event listener inside Shadow DOM
2shadowRoot.innerHTML = `
3 <div id="button-container">
4 <button id="shadow-button">Click Me</button>
5 </div>
6`;
7
8shadowRoot.querySelector('#shadow-button').addEventListener('click', (event) => {
9 console.log('Button : Button clicked inside Shadow DOM');
10 console.log(event.target);
11});
12
13shadowRoot.querySelector('#button-container').addEventListener('click', (event) => {
14 console.log('Container : Button clicked inside Shadow DOM');
15 console.log(event.target);
16});
17
18hostElement.addEventListener('click', (event) => {
19 console.log('Event bubbled to the host element');
20 console.log(event.target);
21});
22
23document.addEventListener('click', (event) => {
24 console.log('Document listener');
25 console.log(event.target);
26});
- 버튼이 클릭되면 두 리스너가 모두 작동하여 이벤트 버블링의 동작을 보여줍니다.
Shadow DOM
내부에서 발생한 이벤트가Light DOM
으로 버블링될 때, 이벤트의target
은 원래의 소스 대신 호스트 요소로 변경됩니다.- 이 예시에서
Shadow DOM
내부의event.target
은 실제button
요소이지만,Shadow DOM
외부에서는 호스트div
요소로 대체됩니다.
- 이 예시에서
Shadow DOM
과 커스텀 요소
Shadow DOM
과 커스텀 요소는 Web Components
의 핵심 구성 요소입니다. 이 기술들은 재사용 가능하고 캡슐화된 UI 컴포넌트를 만들기 위해 사용됩니다.
1class MyCard extends HTMLElement {
2 constructor() {
3 super();
4 const shadow = this.attachShadow({ mode: 'open' });
5 shadow.innerHTML = `
6 <style>
7 p {
8 color: blue;
9 }
10 </style>
11 <p>I'm inside shadow DOM</p>
12 `;
13 }
14}
15
16customElements.define('my-card', MyCard);
메인 문서는 다음과 같은 HTML을 포함합니다:.
1<my-card></my-card>
- 커스텀 요소 내부에서
Shadow DOM
을 사용하면 스타일 충돌에 강한 재사용 가능한 컴포넌트를 만들 수 있습니다. 이 코드에서는my-card
라는 태그가 생성되어MyCard
클래스와 연결됩니다.my-card
내부의<p>
요소는 외부 스타일의 영향을 받지 않으며 항상 파란색으로 표시됩니다.
슬롯: 라이트 DOM
콘텐츠 배포
슬롯을 사용하면 라이트 DOM
콘텐츠를 섀도우 DOM
으로 투영할 수 있습니다. 다음은 예제입니다:.
1class MyElement extends HTMLElement {
2 constructor() {
3 super();
4 const shadow = this.attachShadow({ mode: 'open' });
5
6 shadow.innerHTML = `
7 <style>
8 .container {
9 border: 2px solid #ccc;
10 padding: 16px;
11 border-radius: 8px;
12 font-family: sans-serif;
13 }
14 .header {
15 font-size: 1.2em;
16 color: darkblue;
17 margin-bottom: 8px;
18 }
19 .content {
20 font-size: 1em;
21 color: #333;
22 }
23 </style>
24 <div class="container">
25 <div class="header">
26 <slot name="header"></slot>
27 </div>
28 <div class="content">
29 <slot name="content"></slot>
30 </div>
31 </div>
32 `;
33 }
34}
35
36customElements.define('my-element', MyElement);
메인 문서는 다음과 같은 HTML을 포함합니다:.
1<my-element>
2 <h3 slot="header">Header Content</h1>
3 <p slot="content">Main Content</p>
4</my-element>
섀도우 DOM
내의slot
요소는 해당slot
속성이 있는라이트 DOM
콘텐츠를 표시합니다.
결론
Shadow DOM
은 강력하고 재사용 가능하며 유지보수하기 쉬운 웹 컴포넌트를 만들기 위한 중요한 도구입니다. 스타일과 기능을 캡슐화함으로써 충돌 가능성을 줄이고 코드베이스 관리를 단순화합니다.
위의 기사를 보면서 Visual Studio Code를 사용해 우리 유튜브 채널에서 함께 따라할 수 있습니다. 유튜브 채널도 확인해 주세요.