main-logo

Shadow Dom 가이드

웹 컴포넌트의 캡슐화

profile
nagaeso
2025년 05월 08일 · 0 분 소요

들어가며


지난 포스팅인 innerHTML를 보안하다에서 Shadow DOM에 대해 간단히 언급한 바 있습니다. input, video는 Shadow DOM을 내부적으로 사용하는 대표적인 요소입니다. 그렇다면 왜 DOM 내부에 또 다른 DOM, 즉 Shadow DOM이 필요할까요?

 

 

Shadow DOM이란 무엇일까요?


Shadow DOM HTML 문서 내에서 독립적인 DOM 트리를 생성 있도록 해주는 표준입니다. 마치 HTML 문서 안에 다른 작은 HTML 문서를 품고 있는 것과 같습니다

  • 캡슐화: Shadow DOM 내부의 스타일과 JavaScript는 외부의 영향을 받지 않고, 외부의 코드 또한 Shadow DOM 내부에 영향을 줄 수 없습니다. 이를 통해 코드 충돌을 방지하고, 컴포넌트의 독립성을 보장할 수 있습니다.
  • 독립적인 스코프: Shadow DOM은 자신만의 스코프를 가지므로, 전역으로 선언된 변수나 스타일이 Shadow DOM 내부의 요소에 영향을 미치지 않습니다.

 

가장 친숙한 Shadow Dom은 input의 placeholder입니다. 일반적으로 개발자도구에서는 보이지 않기 때문에 설정을 해주어야 합니다. 

크롬 브라우저의 Shadow DOM 설정

- 개발자 도구 -> Settings -> Preferences -> Elements -> Show user agent shadow DOM 체크

- 체크 후 shadow-root 생성

 

Shadow DOM 구성 요소

  • Shadow Host

    • 정의: Shadow DOM이 부착되는 일반 DOM 노드입니다.

    • 예: <div id="my-component">와 같이 Shadow DOM을 붙일 요소

    • 역할: Shadow Root를 연결하는 진입 지점 역할을 합니다.

  • Shadow Tree

    • 정의: Shadow Root 아래에 위치한 모든 DOM 요소들의 트리 구조

    • 특징: 외부 DOM과 격리되어 있어 스타일이나 이벤트가 캡슐화됩니다.

  • Shadow Boundary

    • 정의: Shadow DOM과 일반 DOM 간의 경계

    • 특징: 일반 DOM에서 Shadow Tree 내부 요소에 접근할 수 없고, 스타일 상속도 제한됩니다.

  • Shadow Root

    • 정의: Shadow Tree의 루트 노드

    • 생성: element.attachShadow({ mode: 'open' })로 생성

    • 역할: Shadow DOM의 진입점이자 최상위 컨테이너입니다.

 

 

Shadow DOM, 왜 사용해야 할까요?


  1. 스타일 충돌 방지: CSS 스타일이 전역적으로 적용되어 예상치 못한 스타일 충돌이 발생하는 것을 막아줍니다. 
  2. 코드 캡슐화: 컴포넌트의 내부 구현을 숨기고, 외부로부터의 접근을 제한하여 코드의 안정성을 높입니다. 
  3. 재사용성 향상: 독립적인 컴포넌트를 만들어 여러 프로젝트에서 재사용할 수 있도록 도와줍니다. 
  4. 유지보수 용이: 각 컴포넌트가 독립적으로 관리되므로, 코드 변경 시 다른 부분에 미치는 영향을 최소화할 수 있습니다. 

 

 

Shadow DOM, 어떻게 만들고 사용할까요?


  1. Shadow Host 선택: Shadow DOM을 부착할 요소를 선택합니다. 이 요소가 Shadow Host가 됩니다.

    const shadowHost = document.querySelector('.my-element');
  2. Shadow Root 생성: attachShadow() 메서드를 사용하여 Shadow Root를 생성합니다. mode 옵션을 통해 Shadow DOM의 접근 가능 여부를 설정할 수 있습니다.

    const shadowRoot = shadowHost.attachShadow({ mode: 'open' });
    • open: 외부 JavaScript에서 shadowRoot 속성을 통해 Shadow DOM에 접근할 수 있습니다.
    • closed: 외부에서 Shadow DOM에 접근할 수 없습니다.
  3. Shadow Tree 구성: innerHTML 속성 또는 DOM API를 사용하여 Shadow Root 내부에 HTML 요소와 스타일을 추가합니다.
    shadowRoot.innerHTML = `
      <style>
        p {
          color: blue;
        }
      </style>
      <p>Hello, Shadow DOM!</p>
    `;

 

 

Shadow DOM, 주의할 점은 없을까요?


  • 접근성: Shadow DOM 내부 요소에 접근하기 위해서는 shadowRoot 속성을 사용해야 합니다. 스크린 리더와 같은 보조 기술과의 호환성을 고려하여 접근성을 확보하는 것이 중요합니다.
    <body>
      <div id="modal-host"></div>
    </body>
    <script>
        // 1. Shadow Host 선택
        const host = document.getElementById('modal-host');
    
        // 2. Shadow Root 생성
        const shadow = host.attachShadow({ mode: 'open' });
    
        // 3. HTML 콘텐츠 작성
        const wrapper = document.createElement('div');
        wrapper.innerHTML = `
          <style>
            .backdrop {
              position: fixed;
              top: 0; left: 0;
              width: 100vw;
              height: 100vh;
              background: rgba(0, 0, 0, 0.5);
              display: flex;
              justify-content: center;
              align-items: center;
            }
            .modal {
              background: white;
              padding: 1rem;
              border-radius: 8px;
              max-width: 300px;
              box-shadow: 0 0 10px rgba(0,0,0,0.3);
            }
            button {
              margin-top: 1rem;
            }
          </style>
    
          <div class="backdrop" role="dialog" aria-modal="true" aria-labelledby="modal-title">
            <div class="modal">
              <h2 id="modal-title">접근성 모달</h2>
              <p>이 모달은 Shadow DOM으로 구성되어 있습니다.</p>
              <button id="close-btn">닫기</button>
            </div>
          </div>
        `;
    
        // 4. Shadow DOM에 내용 추가
        shadow.appendChild(wrapper);
    
        // 5. 이벤트 연결
        const closeBtn = shadow.querySelector('#close-btn');
        const backdrop = shadow.querySelector('.backdrop');
    
        closeBtn.addEventListener('click', () => {
          backdrop.style.display = 'none';
        });
    
        document.addEventListener('keydown', (e) => {
          if (e.key === 'Escape') {
            backdrop.style.display = 'none';
          }
        });
    
        // 6. 포커스 초기 위치 설정
        closeBtn.focus();
    </script>
    • ✅ 접근성 속성 (role, aria-modal, aria-labelledby) 포함

    • ✅ 키보드 ESC로 닫기 및 초기 포커스 지정

  • 이벤트: Shadow DOM 내부에서 발생하는 이벤트는 Shadow Boundary를 넘어 전파되지 않습니다. composed: true 옵션을 사용하여 이벤트가 Shadow Boundary를 넘어 전파되도록 설정할 수 있습니다.
  • 브라우저 지원: Shadow DOM은 대부분의 최신 브라우저에서 지원되지만, IE와 같은 구형 브라우저에서는 지원되지 않을 수 있습니다. 필요에 따라 폴리필(Polyfill)을 사용하여 하위 브라우저를 지원해야 합니다.

 

 

Shadow DOM vs Virtual DOM


 Shadow DOM과 Virtual DOM은 DOM을 다루는 기술이라는 공통점이 있지만, 목적과 동작 방식에서 차이가 있습니다.

구분 Shadow DOM Virtual DOM
목적 스타일/동작 캡슐화, 컴포넌트 격리 DOM 업데이트 최적화
동작 방식 실제 DOM에 숨겨진 DOM 트리 생성 JavaScript 메모리에 가상 DOM을 유지
주요 사용처 웹 컴포넌트, UI 라이브러리 React, Vue.js 등의 UI 라이브러리/프레임워크
스타일 범위 지정 Shadow DOM 내부로 스타일 범위 제한 (별도 기술 필요)

 

 

마치며


Shadow DOM은 웹 컴포넌트 개발에 있어 앞으로도 필수적인 기술입니다. 스타일 충돌 걱정 없이 독립적인 컴포넌트를 개발하고, 코드의 재사용성과 유지보수성을 높일 수 있습니다. 하지만 모든 상황에서 Shadow DOM이 필요한 것은 아니므로, 요구 조건에 맞게, 유연성을 잃지 않도록 만들어야 합니다. 실무에 어떻게 적용하면 좋을지 작은 볼륨부터 고민을 해볼까요 ? 🚀

감사합니다.