FrontEnd/HTML5 & CSS & JavaScript

[HTML/CSS] 쉐도우 돔(Shadow DOM)으로 스타일 충돌 해결하기

푸고배 2023. 5. 6. 16:30

코드로 알아보는 CSS 충돌

다음과 같이 header 컴포넌트(header.vue)header 컴포넌트를 사용하는 content(index.vue)영역이 있다.
 
index.vue

<template>
  <div id="app">
    <Header />
    <div class="content">
      <h3>content의 tittle</h3>
    </div>
  </div>
</template>
...
<style>
@import "../assets/index.css";
</style>

 
index.css

body {
  width: 100%;
  height: 100%;
}

 
header.vue

<template>
  <header id="header">
    <h3>header tittle</h3>
  </header>
</template>
<style>
@import "./assets/header.css";
</style>

 
header에 글자 색상을 회색으로 바꾸고 싶어서 아래와 같이 header.css를 코드를 수정하면 어떻게 될까?
 
header.css

h3 {
  margin: 0 10px;
  color: gray;
}
#header {
  margin: 0;
  width: 1000px;
  height: 50px;
  background-color: black;
  display: flex;
  flex-direction: colunm;
  align-items: center;
}

 
아래와 같이 의도와는 다른 결과를 불러오게 된다.
 
의도 : header의 text만 회색으로 변경
결과 : header와 content의 text 모두 색상이 회색으로 변경
 

 
사용하는 코드에서는 각각의 컴포넌트에 style을 별개로 지정해주는 것 같이 보이지만, 추가한 style은 범위가 제한되지 않는다. (style 태그는 어디에 선언되든 글로벌 영역이기 때문이다.)

기본적인 DOM의 구조

DOM(Document Object Model)은 웹 페이지의 구조와 콘텐츠를 나타내는 계층구조로, HTML의 문서화된 표현이다.
각 요소는 노드(Node)로 표현되며, 각 노드들은 부모-자식 관계를 가지며 트리 형태로 구성된다.
그리고 HTML 문서의 모든 element와 style로 이루어진 DOM은 하나의 큰 글로벌 범위 내에 있다.

css 충돌을 해결하는 다양한 방법

selector로 범위 제한하기

만약, header를 모듈로 제공한다면, content 영역과 완전히 무관한 디펜던시를 보장해야할 것이다.

  • header의 style이 content 영역에 영향을 줘서도 안되고,
  • 반대로 content 영역의 css가 header의 style에 영향을 줘서도 안된다.

간단한 해결방법은 상위 클래스의 selector로 범위를 제한하는 것이다. (ex. #header h3 { ... })
하지만, 이 방법도 다음과 같은 문제가 있다.
 

/** 1. 복잡해지는 style 구조 **/
#header .tittle span {
  ...
}

/** 2. 스타일 리셋 필요 **/
/** index.css **/
h3 {
  margin: 10px;
  padding: 10px;
}
/** header.css **/
#header h3 {
  margin: 0;
  padding: 0;
}

 
1. 범위를 제한하기 위해 css 구조가 복잡해진다.

  • #header .title span { }

2. 범위를 제한하더라도, 공통으로 적용되는 style을 무시할 수 없다.

  • h3 { margin:10px; ... }
  • 따라서 이 경우 공통으로 적용된 style을 리셋시키는 코드가 필요할 것이다.

기본 태그에 주어진 속성을 하나하나 리셋시켜줘야한다니..
 

scoped css(styled component)

HTML5부터는 <style> 요소에 scoped 속성을 사용할 수 있다.
Vue에서는 vue-loader가 제공하는 scoped css 라는 것을 사용해서 특정 컴포넌트에만 해당 스타일을 적용하도록 할 수 있다.
사용 방법도 매우 간단하다. component 내에 선언한 style에 scoped 속성을 추가하면 된다.
 

scoped style은 다음과 같이 data-v-[hash]와 같은 scoped 속성이 추가된다. 

 
다만, 이 방법도 문제점이 있다.

  • v-html 디렉티브로 추가된 element는 scoped 속성이 없다.
  • 하위 컴포넌트에 style이 적용되지 못한다.

두 번째 문제의 경우는 Deep Selector(v-deep, '>>>')로 해결이 가능하지만, 사이드 이펙트를 발생시킬 수 있기 때문에 예외적인 케이스에만 사용하는 것이 좋다.

<style scoped>
.a::v-deep .b { /* ... */ }
</style>

 

iframe

iframe를 이용해서 DOM을 분리하는 경우 아래와 같은 단점이 있다.

  • http 요청이 한차례 더 일어난다
  • 별도의 페이지이기 때문에, 소비되는 리소스도 높고 느리다
  • iframe의 주소가 같은 도메인이 아닌 경우 접근 불가능하다

2016년도에 트위터는 위와 같은 이유로, iframe으로 지원하던 기능을 Shadow DOM 방식으로 전환했다. (지원하지 않는 브라우저는 기존과 동일하게 iframe으로 지원)
Shadow DOM 도입으로 브라우저 메모리 사용률이 훨씬 낮아지고, 렌더링 시간이 빨라졌다고 한다. 

https://twittercommunity.com/t/upcoming-change-to-embedded-tweet-display-on-web/66215

쉐도우 돔(Shadow DOM)이란?

Shadow DOM은 기존의 DOM 트리에서 완전히 분리된 고유의 element와 style을 가진 DOM 트리이다. 
즉, Shadow DOM은 글로벌 범위에 포함되지 않는다. 
Shadow DOM 트리는 Shadow root로부터 시작되어 원하는 모든 요소의 안에 부착될 수 있으며, 방법은 일반 DOM과 동일하다. 
 

Shadow DOM의 장점

Scoped DOM

예를들어 document.querySelector()구성 요소의 Shadow DOM에 있는 노드를 반환하지 않는다.

CSS 단순화

Scoped DOM은 외부와 디펜던시가 없기 때문에, 일반적인 id/class 이름 사용이 가능하다.

생산성

하나의 큰 글로벌 페이지가 아니라 청크된 DOM을 가진다.

Scoped CSS

Shadow DOM 내부에 정의된 CSS는 해당 범위로 지정된다.
스타일 규칙이 외부로 전파되지 않고 외부 스타일의 영향을 받지 않는다.
 

Shadow DOM 관련 용어

Shadow host : Shadow DOM이 부착되는 통상적인 DOM 노드
Shadow tree : Shadow DOM 내부의 DOM 트리
Shadow boundary : Shadow DOM이 끝나고, 통상적인 DOM이 시작되는 장소
Shadow root : Shadow 트리의 root 노드
 

낯설지 않은 Shadow DOM

Shadow DOM이 안보일 때

개발자 도구 설정에서 'Show user agent shadow DOM'을 체크한다. 

Shadow DOM 사용해보기

'element.attachShadow({mode: 'open'})'를 이용해 특정 element에 Shadow DOM을 추가할 수 있다.
mode 옵션의 값으로는 'open'|'close'를 가질 수 있으며 각각은 아래와 같은 의미를 가진다.
 
'open'
ShadowRoot에 포함된 요소에 대해 외부 스크립트에서 접근할 수 있도록 허용한다.
이 모드를 사용하면 외부에서 DOM 트리의 요소에 대한 참조를 가져와서 조작할 수 있다.
'closed'
ShadowRoot에 포함된 요소에 대해 외부 스크립트에서 접근할 수 없다.
이 모드를 사용하면 외부 스크립트에서는 DOM 트리에 포함된 요소에 대한 참조를 가져올 수 없다.

'closed'에서 attachShadow로 element로 접근을 시도할 때는 아래와 같은 오류가 발생한다.


기본값은 closed로, open 모드에서는 :host 선택자를 사용하여 외부에서 스타일을 적용할 수 있다.

 

const header = document.createElement('header');
const shadowRoot = header.attachShadow({mode: 'open'});
shadowRoot.innerHTML = '<h1>Hello Shadow DOM</h1>'; // Could also use appendChild().

// header.shadowRoot === shadowRoot
// shadowRoot.host === header

 
.innerHTML 이외에 textContent 와 같은 다른 DOM API로도 element를 설정할 수 있다. 
shadowRoot 하위로 <style> 태그를 추가하면, 해당 style은 shadowRoot 하위로만 범위가 제한되는 scoped Style로 적용된다.
 
아래는 예시코드로, #header의 h3을 Shadow DOM으로 생성해서 style을 적용했다.
#header의 h3에 적용된 스타일은 content 영역의 h3에 영향을 주지도, 받지도 않는다.

  • header 영역의 h3(외부 영향 X) : {margin: 0 10px; color: gray;}
  • content 영역의 h3 : {background-color: #bbb1a5; padding: 5px;}

 

Shadow DOM을 적용할 수 없는 Element

<textarea>, <input>

브라우저 자체에서 내부 Shadow DOM을 호스팅한다.

<img>

외부 이미지 리소스를 가져와 렌더링하는 태그로, 특별한 컨텐츠를 감싸거나 다른 요소의 자식 요소로 포함되지 않기 때문이다.
 

지원 브라우저

역시나 IE, OperaMini에서는 지원을 하지 않는다.

Polyfill : webcomponentsjs

웹 구성 요소 사양을 지원하는 폴리필 모음 :

이를 필요로 하는 브라우저의 경우 몇 가지 사소한 폴리필도 포함되어 있다.

npm install @webcomponents/webcomponentsjs
import '@webcomponents/webcomponentsjs';
 

GitHub - webcomponents/polyfills: Web Components Polyfills

Web Components Polyfills. Contribute to webcomponents/polyfills development by creating an account on GitHub.

github.com


Reference

 

웹 컴포넌트(3) - 쉐도우 돔(#Shadow DOM)

이 글은 웹 컴포넌트 소개 연재로 그중 세 번째인 쉐도우 돔에 대한 글이다. 아마도 이전 글의 커스텀 엘리먼트 글을 읽고 온 분은 여러 스펙, API, 기억해 두어야 할 것들로 질렸을지도 모르겠다.

ui.toast.com

 

Shadow DOM v1 - Self-Contained Web Components

Shadow DOM allows web developers to create compartmentalized DOM and CSS for web components

web.dev

웹 컴포넌트(3) - 쉐도우 돔(#Shadow DOM)

이 글은 웹 컴포넌트 소개 연재로 그중 세 번째인 쉐도우 돔에 대한 글이다. 아마도 이전 글의 커스텀 엘리먼트 글을 읽고 온 분은 여러 스펙, API, 기억해 두어야 할 것들로 질렸을지도 모르겠다.

ui.toast.com

 

Vue.js Scoped CSS

그리고 몇 가지 이슈들과 해법

blog.jeongwoo.in

 

Shadow DOM: A DOM subtree for encapsulated code blocks

Shadow DOM is one of the core technologies of the popular Web Components suite. In this article, we’ll explain the key elements of this interface in detail.

www.ionos.com

 

쉐도우 돔(Shadow DOM) - JavaScript

쉐도우 돔(Shadow DOM) JavaScript 쉐도우 돔(Shadow DOM)이란 DOM(Document Object Model)은 HTML의 문서화된 표현이다. 그리고 HTML 문서의 모든 요소와 스타일로 이루어진 DOM은 하나의 큰 글로벌 범위 내에 있다. S

mxxcode.tistory.com

반응형