이전 글에서 DOM에 대해서 알아봤습니다. 쉬운 내용은 아니었지만, 브라우저가 어떻게 HTML과 CSS, JS를 렌더링하는지 알 수 있었을 겁니다. 이제는 한단계 올라가봅시다.
HTML의 모든 요소와 Style로 이루어진 DOM은 global한 범위에 있습니다. 어떤 요소가 얼마나 많은 부모를 가지고 있든지 간에, getElementById나 querySelector를 통해서 접근이 가능하죠. CSS도 마찬가지고요. 그 덕분에 우리는 부모는 신경쓰지 않고 모든 요소의 Style을 한꺼번에 변경할 수 있습니다.
div {
color: red;
}
그런데 이렇게 잘 사용하다가 어떤 div와 그에 속한 자식들에게는 color를 blue로 줄 필요가 생겼습니다. 이럴 때는 어떻게 해야 될까요? 바로 이럴 때 필요한 것이 Shadow DOM입니다.
Shadow DOM
단순히 DOM 안의 DOM이라고 생각할 수도 있겠지만, 이것은 원래의 DOM tree에서 분리된 고유의 DOM tree입니다. 다음과 같이 생성할 수 있습니다.
<div class="shadow-root">
</div>
<script>
const shadowElement = document.querySelector('.shadow-root')
// mode option을 open하면 외부 JS에서 엑세스 가능하도록 설정한다는 뜻. closed면 불가능.
const shadow = shadowElement.attachShadow({ mode: 'open' })
shadow.shadowRoot.innerHTML = `<div>I am in the shadow..</div><style> div { color: red; } </style>`
</script>
script쪽을 보시면 특정 Element를 생성한 뒤 attachShadow 메서드를 통해 Shadow root를 생성하는 것을 볼 수 있습니다. 이 아래에 정의된 Element들은 기존에 DOM tree와는 별도로 관리되게 됩니다.
코드를 보면 shadow.shadowRoot에 innerHTML로 <div>와 <style>을 추가한 것을 볼 수 있습니다. 이런 식으로 하면 외부에 있는 <div>들에는 color: red; 값이 적용되지 않고 오직 shadowRoot 안에 있는 <div>에만 적용되게 됩니다. 하지만 이렇게 innerHTML에 template string으로 적는 것은 상당히 불편하기도 하고 예쁘지도 않은 방법입니다. 이럴 때는 <template>을 이용해서 적어준 뒤에 해당 shadowRoot에 바인딩 시켜주는 방법을 사용하는 것이 좋습니다. 아래와 같이 사용할 수 있습니다. 훨씬 깔끔하죠?
<div class="shadow-root">
</div>
<template id="inner">
<div>I am in the shadow..</div>
<style>
div { color: red; }
</style>
</template>
<script>
const shadowElement = document.querySelector('.shadow-root')
// mode option을 open하면 외부 JS에서 엑세스 가능하도록 설정한다는 뜻. closed면 불가능.
const shadow = shadowElement.attachShadow({ mode: 'open' })
shadow.shadowRoot.append(inner.content.cloneNode(true))
</script>