ts
复制代码
// 影子domvideoaudio标签
const template = `<div id="qiankun-xxxx">
<h1 id="inner">bcd</h1>
<style>h1{color:red}</style>
</div>`;
const container = document.createElement("div");
container.innerHTML = template;
const appElement = container.firstChild;
let oldContent = appElement.innerHTML; //老的内容
appElement.innerHTML = "";
const shadow = appElement.attachShadow({ mode: "closed" });
shadow.innerHTML = oldContent; //放到影子dom中
document.body.appendChild(appElement);
// 演示 appendChild 的行为
console.log("=== 演示 appendChild 行为 ===");
const container1 = document.createElement("div");
container1.innerHTML = "<h2>容器1</h2>";
container1.style.border = "2px solid blue";
const container2 = document.createElement("div");
container2.innerHTML = "<h2>容器2</h2>";
container2.style.border = "2px solid green";
console.log("添加第一个容器");
document.body.appendChild(container1);
console.log("body 子元素数量:", document.body.children.length);
console.log("添加第二个容器");
document.body.appendChild(container2);
console.log("body 子元素数量:", document.body.children.length);
console.log("再次添加第一个容器(应该移动而不是复制)");
document.body.appendChild(container1);
console.log("body 子元素数量:", document.body.children.length);
console.log("第一个容器现在在最后位置");
// 如果你想创建多个相同的元素,需要创建新的节点
console.log("=== 创建多个相同元素的方法 ===");
const container3 = container1.cloneNode(true); // 克隆节点
container3.style.border = "2px solid red";
container3.querySelector("h2").textContent = "容器3(克隆)";
document.body.appendChild(container3);
console.log("现在有3个容器了");
// ========== Shadow DOM 核心概念演示 ==========
//
// 关键理解:
// 1. shadowDOMContainer 是"宿主元素"(host element)
// 2. shadow1 是"ShadowRoot"(影子根),它是通过 attachShadow() 创建的
// 3. 一旦创建了 Shadow DOM,宿主元素的直接子元素在"视觉上"被 Shadow DOM 覆盖
// 注意:这些子元素仍然存在于 DOM 树中,可以通过 DOM API 访问,但不会在视觉上显示
// 4. Shadow DOM 的内容必须通过 ShadowRoot 来设置,而不是通过宿主元素
//
// 为什么不能通过 shadowDOMContainer.innerHTML 设置 Shadow DOM 内容?
// 答:因为 shadowDOMContainer.innerHTML 设置的是"宿主元素"的内容,
// 而 Shadow DOM 是一个独立的、封装的 DOM 树,它存在于 ShadowRoot 中。
// 两者是完全隔离的!宿主元素的内容在 DOM 树中存在,但在视觉上被 Shadow DOM "覆盖"。
//
// 重要区别:
// - DOM 树存在:宿主元素的子内容确实存在于 DOM 树中(可以通过 querySelector 等访问)
// - 视觉渲染:但在浏览器渲染时,这些内容被 Shadow DOM 的内容"替换"了(不显示)
// 这是 Shadow DOM 的"扁平化"(Flattening)机制
console.log("=== Shadow DOM 工作原理演示 ===");
// 创建一个影子dom容器(宿主元素)
const shadowDOMContainer = document.createElement("div");
shadowDOMContainer.innerHTML = "<h2>影子dom容器(这个不会显示)</h2>";
shadowDOMContainer.style.border = "2px solid purple";
shadowDOMContainer.style.padding = "10px";
shadowDOMContainer.style.margin = "10px";
// 创建 Shadow DOM
const shadow1 = shadowDOMContainer.attachShadow({ mode: "open" });
// ✅ 正确:通过 ShadowRoot 设置 Shadow DOM 的内容
shadow1.innerHTML = `
<style>
h2 { color: blue; background: yellow; padding: 10px; }
p { color: green; }
</style>
<h2>这是 Shadow DOM 中的内容(会显示)</h2>
<p>Shadow DOM 的内容是封装的,外部样式不会影响它</p>
`;
console.log("shadow1 (ShadowRoot):", shadow1);
console.log("shadowDOMContainer (宿主元素):", shadowDOMContainer);
console.log(
"shadowDOMContainer.innerHTML:",
shadowDOMContainer.innerHTML
);
console.log("shadow1.innerHTML:", shadow1.innerHTML);
// 演示:即使修改宿主元素的 innerHTML,也不会影响 Shadow DOM
console.log("\n=== 演示:宿主元素和 Shadow DOM 的隔离性 ===");
setTimeout(() => {
shadowDOMContainer.innerHTML = "<h2>我修改了宿主元素,但不会显示</h2>";
console.log(
"修改后 shadowDOMContainer.innerHTML:",
shadowDOMContainer.innerHTML
);
console.log("但 Shadow DOM 的内容不变:", shadow1.innerHTML);
}, 2000);
// ❌ 错误:ShadowRoot 不是 DOM 节点,不能直接 appendChild
// document.body.appendChild(shadow1); // 这会报错:Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'
// ✅ 正确:必须把宿主元素(shadowDOMContainer)添加到 DOM 树
// ShadowRoot 是宿主元素的一个属性,不是独立的 DOM 节点
// 只有通过宿主元素,Shadow DOM 才能成为 DOM 树的一部分
document.body.appendChild(shadowDOMContainer);
// ========== 对比示例:普通 DOM vs Shadow DOM ==========
console.log("\n=== 对比:普通 DOM 和 Shadow DOM ===");
// 普通 DOM:直接通过 innerHTML 设置内容
const normalDiv = document.createElement("div");
normalDiv.innerHTML = "<h2>普通 DOM:直接显示</h2>";
normalDiv.style.border = "2px solid orange";
normalDiv.style.padding = "10px";
normalDiv.style.margin = "10px";
document.body.appendChild(normalDiv);
// Shadow DOM:必须先创建 ShadowRoot,然后通过 ShadowRoot 设置内容
const shadowDiv = document.createElement("div");
shadowDiv.style.border = "2px solid red";
shadowDiv.style.padding = "10px";
shadowDiv.style.margin = "10px";
// ⚠️ 注意:这样设置的内容在 DOM 树中存在,但在视觉上不会显示(被 Shadow DOM 覆盖)
shadowDiv.innerHTML =
"<h2>这个在 DOM 树中存在,但视觉上不会显示(被 Shadow DOM 覆盖)</h2>";
// ✅ 正确:创建 Shadow DOM 后,通过 ShadowRoot 设置内容
const shadowRoot = shadowDiv.attachShadow({ mode: "open" });
shadowRoot.innerHTML = "<h2>Shadow DOM:通过 ShadowRoot 设置的内容</h2>";
document.body.appendChild(shadowDiv);
console.log("普通 DOM innerHTML:", normalDiv.innerHTML);
console.log("Shadow DOM 宿主元素 innerHTML:", shadowDiv.innerHTML);
console.log("Shadow DOM ShadowRoot innerHTML:", shadowRoot.innerHTML);
// ========== 重要概念澄清:DOM 树存在 vs 视觉渲染 ==========
console.log("\n=== 重要概念:DOM 树存在 vs 视觉渲染 ===");
const demoDiv = document.createElement("div");
demoDiv.id = "shadow-demo";
demoDiv.style.border = "3px solid #333";
demoDiv.style.padding = "15px";
demoDiv.style.margin = "15px";
demoDiv.style.backgroundColor = "#f0f0f0";
// 1. 先设置宿主元素的内容(这些内容会存在于 DOM 树中)
demoDiv.innerHTML = `
<h3 id="host-child-1">宿主元素的子元素1(DOM 树中存在,但视觉上被覆盖)</h3>
<p id="host-child-2">宿主元素的子元素2(DOM 树中存在,但视觉上被覆盖)</p>
<span id="host-child-3">宿主元素的子元素3(DOM 树中存在,但视觉上被覆盖)</span>
`;
console.log("\n【步骤1】创建 Shadow DOM 之前:");
console.log("demoDiv.children.length:", demoDiv.children.length);
console.log("demoDiv.innerHTML:", demoDiv.innerHTML);
console.log(
"可以通过 DOM API 访问子元素:",
demoDiv.querySelector("#host-child-1")
);
// 2. 创建 Shadow DOM
const demoShadowRoot = demoDiv.attachShadow({ mode: "open" });
console.log("\n【步骤2】创建 Shadow DOM 之后:");
console.log(
"demoDiv.children.length:",
demoDiv.children.length,
"(子元素仍然存在!)"
);
console.log(
"demoDiv.innerHTML:",
demoDiv.innerHTML,
"(内容仍然存在!)"
);
console.log(
"可以通过 DOM API 访问子元素:",
demoDiv.querySelector("#host-child-1"),
"(仍然可以访问!)"
);
console.log("但是这些元素在视觉上被 Shadow DOM 覆盖了");
// 3. 设置 Shadow DOM 的内容
demoShadowRoot.innerHTML = `
<style>
.shadow-content {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
border-radius: 8px;
}
.shadow-content h3 {
margin-top: 0;
color: #ffd700;
}
</style>
<div class="shadow-content">
<h3>这是 Shadow DOM 中的内容(视觉上会显示)</h3>
<p>Shadow DOM 的内容会"覆盖"宿主元素的直接子内容</p>
<p>但宿主元素的子内容仍然存在于 DOM 树中,只是不参与视觉渲染</p>
</div>
`;
document.body.appendChild(demoDiv);
// 4. 验证:宿主元素的子内容确实存在于 DOM 树中
setTimeout(() => {
console.log("\n【步骤3】验证 DOM 树结构:");
console.log("=== 宿主元素的子节点(DOM 树中存在)===");
Array.from(demoDiv.children).forEach((child, index) => {
console.log(
`子元素 ${index + 1}:`,
child.tagName,
child.id,
child.textContent
);
});
console.log("\n=== Shadow DOM 的内容(视觉上显示)===");
console.log(
"\n=== Shadow DOM 的内容(视觉上显示)===",
demoDiv.shadowRoot
);
console.log("ShadowRoot 的子节点:", demoShadowRoot.children.length);
Array.from(demoShadowRoot.children).forEach((child, index) => {
console.log(
`Shadow 子元素 ${index + 1}:`,
child.tagName,
child.className
);
});
// 5. 尝试通过 JavaScript 操作宿主元素的子内容
console.log("\n【步骤4】尝试操作宿主元素的子内容:");
const hostChild = demoDiv.querySelector("#host-child-1");
if (hostChild) {
console.log("✅ 可以访问宿主元素的子元素:", hostChild);
console.log("✅ 可以修改宿主元素的子元素:", hostChild.textContent);
hostChild.style.color = "red"; // 这个样式不会显示,因为元素被 Shadow DOM 覆盖
hostChild.textContent =
"我修改了文本,但你看不到(被 Shadow DOM 覆盖)";
console.log("修改后的文本:", hostChild.textContent);
console.log("⚠️ 但是视觉上看不到,因为被 Shadow DOM 覆盖了");
}
// 6. 演示 Shadow DOM 的"扁平化"(Flattening)机制
console.log("\n【步骤5】Shadow DOM 的扁平化机制:");
console.log(`
Shadow DOM 的工作原理:
1. 宿主元素的直接子内容确实存在于 DOM 树中(可以通过 DOM API 访问)
2. 但在浏览器渲染时,这些内容被 Shadow DOM 的内容"替换"了
3. 这是 Shadow DOM 的"扁平化"(Flattening)机制
4. 视觉上只显示 Shadow DOM 的内容,但 DOM 树中两者都存在
`);
}, 1000);