什么是shadow Dom?有什么用?

2,648 阅读2分钟

前言

近期在看微前端框架以及qiankun的源码时,发现qiankun实现css样式隔离时,使用了shadowDom。于是出于好奇,去探索了一部分这方面的知识。

什么是shadowDom?

在我们日常调试页面代码时,应该都遇到过如下以 # 开头的元素,这种就是shadowDom:

image.png

你的 video 标签没有shadowDom?自行搜索 chrome浏览器调试配置,打开 show user agent shadow Dom 选项

我们在代码中,其实只是写了一个 video 标签或 input 标签,但是在页面中,我们发现实际渲染了很多我们没有写的元素,比如 video 标签下的播放暂停按钮,进度条等。这些其实都是浏览器来加入的shadow Dom。那么这个video标签的实际dom tree是这样的:

image.png

这里,有一些 Shadow DOM 特有的术语需要我们了解:

  • Shadow host:一个常规 DOM节点,Shadow DOM 会被附加到这个节点上。
  • Shadow tree:Shadow DOM内部的DOM树。
  • Shadow boundary:Shadow DOM结束的地方,也是常规 DOM开始的地方。
  • Shadow root: Shadow tree的根节点。

有什么特点?

  • 可以使用和操作常规 DOM 一样的方式来操作 Shadow DOM,例如添加子节点、设置属性,以及为节点添加自己的样式
  • Shadow DOM 内部的元素始终不会影响到它外部的元素(除了 :focus-within),这为封装提供了便利。

有什么用?

  • 良好的封装性,对于一个通用组件来说,我们作为开发者,其实并不需要关心内部很多元素的实现或原理,比如我们在页面中使用 video 标签,其实对于暂停,播放,拖动滚动条等逻辑并不需要做过多的关心,那么我们希望代码越简洁越好,就像现在一样,只需要一个video标签即可,而不是还要写一堆元素来实现本身的播放暂停等按钮

怎么用?

  • 使用 attachShadow 创建shadowDom:
let shadow = element.attachShadow({mode: 'open'});
let shadow = element.attachShadow({mode: 'closed'});
    • open / closed 表示可否通过页面内的 JavaScript 方法来获取 Shadow DOM

试试?

  • 示例中有两个div的className都是haha,一个在普通dom中,一个在shadowDom中。
  • 可以看到两个style标签下的haha样式互不影响
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .haha {
      background-color: pink;
    }
  </style>
</head>
<body>
  <div class="wrapper">
    <div class="inner"></div>
    <div class="haha">哈哈哈哈</div>
  </div>
</body>
<script>
  const inner = document.querySelector('.inner');
  const innerShadow = inner.attachShadow({mode: 'open'});

  const div = document.createElement('div');
  div.innerHTML = "haha";
  div.className = 'haha'

  const style = document.createElement('style');
  style.textContent = `
    .haha {
      color: #ff0000;
    }
  `;
  
  innerShadow.appendChild(style)
  innerShadow.appendChild(div)
</script>
</html>

效果: image.png