简单了解 shadowDom

Shadow DOM 的工作原理:

  • 宿主元素的直接子内容确实存在于 DOM 树中(可以通过 DOM API 访问)
  • 但在浏览器渲染时,这些内容被 Shadow DOM 的内容"替换"了
  • 这是 Shadow DOM 的"扁平化"(Flattening)机制
  • 视觉上只显示 Shadow DOM 的内容,但 DOM 树中两者都存在
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);
相关推荐
BBB努力学习程序设计2 小时前
Bootstrap图片:让图片展示更优雅、更专业
前端·html
玉宇夕落2 小时前
深入理解 async/await:从原理到实战,彻底掌握 JavaScript 异步编程
前端
Sailing2 小时前
🚀 Promise.then 与 async/await 到底差在哪?(这次彻底讲明白)
前端·javascript·面试
鹤鸣的日常2 小时前
Vue + element plus 二次封装表格
前端·javascript·vue.js·elementui·typescript
JarvanMo2 小时前
Flakeproof - 自动化 Flutter 的用户体验 (UX) 测试
前端
北慕阳2 小时前
速成Vue,自己看
前端·javascript·vue.js
shanyanwt2 小时前
1分钟解决iOS App Store上架图片尺寸问题
前端·ios
摇滚侠2 小时前
HTML5,CSS3,开启浮动布局后,主轴和侧轴的概念
前端·css3·html5
少云清2 小时前
【软件测试】4_基础知识 _HTML
前端·html