本文作者为 360 奇舞团前端开发工程师
什么是 Shadow DOM
Shadow DOM 简介
在现代的前端开发中,组件化和模块化已经成为构建可维护和可扩展 Web 程序的关键。随着 Web 程序规模的增长,很容易遇到样式和逻辑冲突的问题。为了解决这些问题,Web 标准引入了 Shadow DOM,可以帮助我们更好地隔离和封装组件。提到 Shadow DOM 就不得不提 Web Components。Shadow DOM 是 Web Components 标准的一部分,是 Web Components 中的一个关键技术。它提供了一种将 HTML 结构、样式和行为封装在一个独立的、封闭的 DOM 中的机制。这意味着,使用 Shadow DOM 可以将组件的样式和结构隐藏在组件作用域内,防止其与全局样式或逻辑发生冲突。
Web Components 简介
既然 Shadow DOM 是 Web Components 标准的一部分,那么什么是 Web Components?Web Components 是旨在帮助开发者能够创建可重用的组件,这些组件可以在不同的 Web 应用程序中使用的技术。Web Components 由以下三项主要技术组成:
Custom Elements: 一组 JavaScript API,允许开发者定义自己的 HTML 元素和其行为。
Shadow DOM: 一组 JavaScript API,提供了一种将 HTML 结构、样式和行为封装在一个独立的、可隔离的作用域内的机制。
HTML Templates: 允许开发者定义 HTML 结构的模板,可以作为自定义元素结构的基础被多次重用。
尽管 Shadow DOM 是 Web Components 中的一个关键技术,如果只使用 Shadow DOM 来隔离样式和封装 DOM 结构,也可以单独使用 Shadow DOM,而不定义 Custom Elements 和使用其他 Web Components 技术。比如下面我们先看一下简单的例子。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shadow DOM</title>
</head>
<body>
<div id="shadow-element">This content is outside Shadow DOM.</div>
<script>
const hostElement = document.getElementById('shadow-element');
const shadowRoot = hostElement.attachShadow({ mode: 'open' });
const paragraph = document.createElement('p');
paragraph.textContent = 'This content is inside Shadow DOM.';
shadowRoot.appendChild(paragraph);
</script>
</body>
</html>
运行效果如下:
Shadow DOM 核心概念
Shadow DOM 允许将隐藏的 DOM 树附加到常规的 DOM 树中——它以 shadow root 节点为起始根节点,在这个根节点的下方,可以是任意元素,和普通的 DOM 元素一样。
Shadow host:一个常规 DOM 节点,作为 Shadow DOM 的容器。Shadow DOM 会被附加到这个节点上。Shadow host 将 Shadow DOM 插入到文档中,作为 Shadow DOM 的入口点。它是外部 DOM 和 Shadow DOM 之间的连接点。
Shadow tree:Shadow DOM 内部的 DOM 结构。包含完整的的 HTML 结构、样式和行为,形成一个独立的渲染上下文。
Shadow boundary:Shadow DOM 结束的地方,也是常规 DOM 开始的地方。确保样式和 DOM 结构的隔离。
Shadow root: Shadow tree 的根节点。包含 Shadow DOM 的所有内容,它是 Shadow DOM 内部结构的入口。通过创建 Shadow Root,可以将 Shadow DOM 附加到 Shadow host 上。
Shadow DOM 都不是一个新事物——在过去的很长一段时间里,浏览器用它来封装一些元素的内部结构。以一个有着默认播放控制按钮的
<video>
元素为例。你所能看到的只是一个<video>
标签,实际上,在它的 Shadow DOM 中,包含了一系列的按钮和其他控制器。Shadow DOM 标准允许你为你自己的元素(custom element)维护一组 Shadow DOM。
我们可以 Dev Tools 中通过 Network -> Add -> Preferences -> Show user agent shadow DOM 来查看 video 的内部信息(操作步骤如下)。
Shadow DOM 使用
基本用法
要在项目中使用Shadow DOM,需要以下步骤:
获取/创建 Shadow host 作为 Shadow DOM 的容器。
创建 Shadow DOM :使用元素的 attachShadow 方法来创建 Shadow DOM。这个方法接受一个参数,即一个包含 mode 属性的配置对象,该属性可以是 'open' 或 'closed'。'open' 表示可以通过 JavaScript 访问 Shadow DOM,而 'closed' 则表示无法通过 JavaScript 直接访问。
定义 Shadow DOM 的内容:在创建的 Shadow DOM 中,你可以添加自己的 HTML 结构和样式。
在开始之前先看下面的代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shadow DOM Example</title>
<style>
button {
background-color: black;
color: #fff;
padding: 10px 20px;
font-size: 16px;
}
</style>
</head>
<body>
<button>normal</button>
<div id="shadow-container"></div>
<script>
const buttonContainer = document.getElementById('shadow-container');
const shadow = buttonContainer.attachShadow({ mode: 'open' });
const style = document.createElement('style');
style.textContent = `
button {
background-color: red;
color: #fff;
padding: 10px 20px;
font-size: 16px;
}
`;
const button = document.createElement('button');
button.textContent = 'In Shadow DOM';
shadow.appendChild(style);
shadow.appendChild(button);
</script>
</body>
</html>
在上面的代码中,我们创建了一个普通按钮和一个使用Shadow DOM的按钮。Shadow DOM中的按钮样式独立于页面上的全局样式,因此它具有不同的背景颜色(红色)。Shadow DOM的一个重要特性是它提供了封装性,使得内部的样式和结构不会影响到外部的文档。
Shadow DOM Mode
同时我们可以注意到 { mode: 'open' }
传的是 open,mode 的取值为 open 和 closed
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shadow DOM Example</title>
</head>
<body>
<div>
<p>paragraph</p>
</div>
<div id="host"></div>
<script>
const ele = document.querySelector('#host');
const shadowRoot = ele.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `<p>Shadow DOM</p>`;
ele.shadowRoot.querySelector('p').innerText = 'change from outside';
ele.shadowRoot.querySelector('p').style.color = 'red';
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shadow DOM Example</title>
</head>
<body>
<div>
<p>paragraph</p>
</div>
<div id="host"></div>
<script>
const ele = document.querySelector('#host');
const shadowRoot = ele.attachShadow({ mode: 'closed' });
shadowRoot.innerHTML = `<p>Shadow DOM</p>`;
ele.shadowRoot.querySelector('p').innerText = 'change from outside';
ele.shadowRoot.querySelector('p').style.color = 'red';
</script>
</body>
</html>
通过上面的例子可以看到,open
模式允许外部访问Shadow DOM,而closed
模式则不允许。
Shadow DOM 优势
封装:Shadow DOM允许开发者将组件的HTML、样式和行为封装在一起,使其成为一个独立的单元。这有助于降低组件之间的耦合度。
隔离:Shadow DOM内部的DOM结构对外部不可见,从而避免了全局作用域的样式和脚本冲突。这有助于提高代码的可靠性。
Shadow DOM 劣势
学习曲线:使用和理解 Shadow DOM 需要一定的学习曲线,特别是对于那些不熟悉 Web 组件概念的开发者。
调试困难:由于 Shadow DOM 的封装性质,调试组件内部的 DOM 结构和样式可能变得更加困难。
浏览器支持:虽然大多数现代浏览器都支持 Shadow DOM,但在某些特定环境或设备上,可能会遇到兼容性问题。
Shadow DOM 和 Iframe
Shadow DOM 更适合于构建具有封装性和局部样式的组件,而 <iframe>
更适合于加载独立的文档、应用程序或提供跨域通信的场景。
Shadow DOM 使用场景
笔者使用 Shadow DOM 的场景是在开发 Chrome Extension 过程中为了避免 Chrome Extension 注入的页面与原页面产生样式冲突,因此选择了 Shadow DOM 。如果有组件化开发、样式隔离、浏览器插件开发、Web组件库等场景时可以选择 Shadow DOM 。
参考资料
https://developer.mozilla.org/zh-CN/docs/Web/API/Web_components
https://developer.mozilla.org/zh-CN/docs/Web/API/Web_components/Using_shadow_DOM
- END -
关于奇舞团
奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。