前言
近期在看微前端框架以及qiankun的源码时,发现qiankun实现css样式隔离时,使用了shadowDom。于是出于好奇,去探索了一部分这方面的知识。
什么是shadowDom?
在我们日常调试页面代码时,应该都遇到过如下以 # 开头的元素,这种就是shadowDom:
你的 video 标签没有shadowDom?自行搜索 chrome浏览器调试配置,打开 show user agent shadow Dom 选项
我们在代码中,其实只是写了一个 video 标签或 input 标签,但是在页面中,我们发现实际渲染了很多我们没有写的元素,比如 video 标签下的播放暂停按钮,进度条等。这些其实都是浏览器来加入的shadow Dom。那么这个video标签的实际dom tree是这样的:
这里,有一些 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>
效果: