生产活动中遇到将多个html注入到一个index.html的需求,如果使用文本处理或者直接将页面内容分类注入的话,会产生很多诸如命名冲突、逻辑冲突、布局混乱等问题,这时候就需要将不同子网页内容隔离开来,也就需要用到Shadow Dom
Shadow DOM(影子 DOM)是 Web Components 技术栈中的关键特性之一,它允许将封装的、独立的 DOM 子树附加到常规 DOM 树中的元素上,从而实现样式和标记的封装。
核心概念
- DOM 封装
Shadow DOM 内部的元素不会暴露给外部 DOM 的选择器(如 querySelector、CSS 选择器)。
样式隔离:外部样式不会影响 Shadow DOM 内部,内部样式也不会影响外部(默认情况下)。
- 创建方式
const element = document.createElement('div');
// 创建 shadow root(有两种模式)
const shadowRoot = element.attachShadow({ mode: 'open' }); // 可通过 element.shadowRoot 访问
// const shadowRoot = element.attachShadow({ mode: 'closed' }); // shadowRoot 为 null,不可外部访问
// 向 shadow DOM 添加内容
shadowRoot.innerHTML = `
<style>
p { color: blue; }
</style>
<p>这段文字在 Shadow DOM 内部</p>
`;
主要特点
- 样式封装
外部 CSS 不会影响 Shadow DOM 内部(除了一些继承属性如字体、颜色等)。
内部 CSS 不会影响外部元素。
可使用 :host 伪类选择器定义宿主元素的样式:
:host {
display: block;
border: 1px solid #ccc;
}
- 插槽(Slots)
允许在自定义组件中插入外部内容。
<!-- 自定义组件模板 -->
<template id="my-card">
<div class="card">
<slot name="title">默认标题</slot>
<slot>默认内容</slot>
</div>
</template>
<!-- 使用 -->
<my-card>
<h1 slot="title">自定义标题</h1>
<p>这是卡片内容</p>
</my-card>
- 事件处理
事件在 Shadow DOM 内部触发时,外部默认看到的是宿主元素的事件(可通过 event.composedPath() 查看事件路径)。
需要跨边界的事件需使用 composed: true:
new CustomEvent('my-event', {
bubbles: true,
composed: true
});
实际应用场景
Web Components 开发(结合 Custom Elements 和 Templates)
封装第三方小部件(如视频播放器、复杂 UI 控件)
样式隔离的组件库
避免 CSS 类名冲突
示例:简单自定义组件
<user-card name="张三"></user-card>
<script>
class UserCard extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
:host { display: inline-block; padding: 10px; border: 1px solid #ddd; }
.name { color: red; font-weight: bold; }
</style>
<div>用户名:<span class="name">${this.getAttribute('name')}</span></div>
`;
}
}
customElements.define('user-card', UserCard);
</script>
特别注意
由于隔离性,要注入的html的script部分不能再用document去获取shadow dom自身的element,而使用shadow,这个shadow表示对于此部件自身的shadowDom
想要子html调用,需要在主脚本负责注入逻辑处将shadow暴露出来,如
const shadowRoot = (component as any).__shadow;
scripts.forEach(code => {
// 直接执行,并把 shadowRoot 作为参数传进去
const func = new Function('shadowRoot', code);
func.call(shadowRoot, shadowRoot);
});
然后子脚本就可以通过shadowRoot获取到shadow中的元素、样式标签等
Shadow DOM 是实现组件级封装的强大工具,特别适合构建可复用、隔离性强的 UI 组件。
此方悬停