认识
一、认识
Shadow DOM
(影子 DOM
)是 Web 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>