跳到主要内容

认识

一、认识


Shadow DOM(影子 DOMWeb Components 技术中的核心概念之一, 它允许你将一个 DOM 子树与主文档 DOM 隔离开来,从而实现组件级别的封装。Shadow DOM 可以将一个元素的内部结构、样式和行为与外部文档分离开。这样一来,组件内部的样式不会受到全局样式的干扰,也不会影响到外部的样式。通过 Shadow DOM, 你可以创建自包含的组件。组件内部的实现细节对外部是不可见的,仅暴露必要的接口或插槽(slot)给外部使用。

你可以使用同样的方式来操作 Shadow DOM,就和操作常规 DOM 一样——例如添加子节点、设置属性,以及为节点添加自己的样式(例如通过 element.style 属性),或者为整个 Shadow DOM 添加样式(例如在 <style> 元素内添加样式)。不同的是,Shadow DOM 内部的元素始终不会影响到它外部的元素(除了 :focus-within),这为封装提供了便利。

1.1 术语

Shadow DOM 相关的术语如下:

  • Shadow Host: 一个常规 DOM 节点,Shadow DOM 会被附加到这个节点上

  • Shadow Tree: Shadow DOM 内部的 DOM

  • Shadow Boundary: Shadow DOM 结束的地方,也是常规 DOM 开始的地方

  • Shadow Root: Shadow Tree 的根节点

1.2 优点

1.3 缺点

ShadowDom 是游离在DOM树之外的节点树,但是他的创建基于普通DOM元素(并不文档的DOM树),所有没有办法基于整个文档的DOM树来直接进行操作。如果要操作一个 ShadowDOM , 往往需要定位到 Shadow DOM 的宿主节点, 然后切换到 Shadow Root 中, 然后再选择 Shadow Root 中对应的节点。

二、语法


shadowRoot 作为隐藏的起始根节点, 同样可以插入 <link><style> 以及其他内置标签元素。不过需要注意的是: <link> 元素不会打断 shadow root 的绘制,因此在加载样式表时可能会出现未添加样式内容(FOUC),导致闪烁。

2.1 内置元素创建 Shadow DOM

index.js 方式一

const appEl = document.getElementById("app");
const shadowRoot = appEl.attachShadow({ mode: "open" });

shadowRoot.innerHTML = `
<style>
.wrapper{
width: 100px;
height: 100px;
background-colo: red;
}
</style>

<div class="wrapper">
嘻嘻哈哈
</div>
`;

index.js 方式二

const app = document.querySelector('#app');
const shadowDom = app.attachShadow({ mode: 'open'});

const div1 = document.createElement('div');
div1.classList.add('div1');
div1.innerHTML = 'shadowDom 元素内容'
const style = document.createElement('style');
style.textContent = `
.div1{
color: red;
}
`

const linkElem = document.createElement("link");
linkElem.setAttribute("rel", "stylesheet");
linkElem.setAttribute("href", "style.css");


shadowDom.appendChild(div1);
shadowDom.appendChild(style);
shadowDom.appendChild(linkElem);
<div id="app"></div>

2.2 自定义元素创建 Shadow DOM

index.js 方式一

class MyElement extends HTMLElement {
constructor() {
super();
}

connectedCallback() {
const shadowRoot = this.attachShadow({ mode: "ope" });

shadowRoot.innerHTML = `
<style>
.container {
border: 1px solid #ccc;
padding: 10px;
border-radius: 5px;
}
header, footer {
background-color: #f0f0f0;
padding: 5px;
}
</style>
<div class="container">
<header>
<!-- 具名插槽:header -->
<slot name="header">默认头部内容</slot>
</header>
<main>
<!-- 默认插槽 -->
<slot>默认主体内容</slot>
</main>
<footer>
<!-- 具名插槽:footer -->
<slot name="footer">默认底部内容</slot>
</footer>
</div>
`;
}
}

customElements.define("my-component", MyElement);

index.js 方式二

class CustomElement extends HTMLElement{
constructor(){
super();

const shadow = this.attachShadow({ mode: "open" });
const wrapper = document.createElement('div');
wrapper.innerHTML = "自主定制元素"
wrapper.classList.add('wrapper');
const style = document.createElement('style');
style.textContent = `
.wrapper{
color: red;
}
`

console.log(this.getAttribute('id'));
console.log(this.hasAttribute('url'))

const linkElem = document.createElement("link");
linkElem.setAttribute("rel", "stylesheet");
linkElem.setAttribute("href", "style.css");

shadow.appendChild(wrapper);
shadow.appendChild(style);
shadow.appendChild(linkElem);
}
}

customElements.define('custom-element',CustomElement);
<my-component>
<h2 slot="header">这是自定义头部</h2>
<p>这是自定义主体内容,会替换默认插槽。</p>
<small slot="footer">这是自定义底部</small>
</my-component>

<custom-element id="3" url="http"></custom-element>