为什么你的 Custom Elements 没有在 ShadowDOM 中正确呈现

在 Web 开发中,我们经常需要动态生成并使用自定义元素(Custom Elements),它们能够让我们更方便、更灵活地操纵网页结构。同时,ShadowDOM 提供了一种机制,使得我们可以将一组 HTML 元素封装到一个独立的作用域中,从而隔离不同组件的样式和行为。但是,当我们同时使用 Custom Elements 和 ShadowDOM 时,有时会遇到奇怪的问题,比如我们期望的自定义元素并没有在 ShadowDOM 中正确呈现。下面,我们就来分析一下其中的原因和解决方法。

原因

在大多数情况下,我们使用 Custom Elements 可能会遇到两种情况。

Case 1

我们定义的自定义元素没有在 ShadowDOM 中显示,而是被作为根节点添加到了文档中。

--------- -----
------
------
  -------
    ------------------ -
      -------- ------- -----------
    -
    ----------------- -
      -------- ------ ----------
    -
  --------
  --------
    ----- --------- ------- ----------- -
      ------------- -
        --------
        ----- ------ - ------------------- ----- ------ ---
        ----- ------- - ------------------------------
        ---------- - ----------
        ----- ------- - ------------------------------
        ---------- - ----------
        ----------------- - -----------
        -----------------------------
        ----------------------------
      -
    -
    ----------------------------------- -----------
  ---------
-------
------
  ----------- --------------------------
  ---- ---------------------
-------
-------

这个例子中,我们定义了一个自定义元素 my-element,在它的构造函数中创建了 ShadowDOM,并将一个自定义的 DOM 树添加到了其中。然后,我们在文档中加入了这个自定义元素和一个用来模拟作用域的 <div> 元素 #container

如果我们在浏览器中打开这个页面,会发现自定义元素并没有在 ShadowDOM 中,而是变成了作为根节点的 #wrapper 元素、#container 元素的下一个兄弟节点。此外,我们定义的 ::before::after 伪元素也没有产生任何效果。如下图所示:

Case 2

我们定义的自定义元素在 ShadowDOM 中显示,但是样式表无法应用到它身上,导致它的外观不正常。

--------- -----
------
------
  -------
    ------------------ -
      -------- ------- -----------
    -
    ----------------- -
      -------- ------ ----------
    -
    ---------- -
      ------- --- ----- ----
    -
  --------
  --------
    ----- --------- ------- ----------- -
      ------------- -
        --------
        ----- ------ - ------------------- ----- ------ ---
        ----- ------- - ------------------------------
        ---------- - ----------
        ----- ------- - ------------------------------
        ---------- - ----------
        ----------------- - -----------
        -----------------------------
        ----------------------------
      -
    -
    ----------------------------------- -----------
  ---------
-------
------
  ---- ---------------
    ----------- --------------------------
  ------
-------
-------

这个例子中,我们和上面的例子非常相似,只是将自定义元素放到了 <div> 元素 #container 内部。此外,我们添加了一个简单的 CSS 样式表,它为自定义元素添加了一个红色的边框。

如果我们在浏览器中打开这个页面,会发现自定义元素被正确封装在 ShadowDOM 中,但是它的外观并没有应用上我们定义的样式。如下图所示:

解决方法

为了解决上述问题,我们需要了解 Custom Elements 和 ShadowDOM 的一些细节:

Case 1

首先,我们可以使用标准的 DOM API(比如 document.createElement())代替自定义元素,然后将它们添加到 ShadowDOM 中。这样虽然虽然不能发挥 Custom Elements 的优势,但是能够保证 DOM 的正确性和布局。

其次,我们需要明确一个概念:自定义元素的“内部”和“外部”是不同的。当我们将 <my-element> 添加到文档中时,它的内部是 ShadowDOM 的 DOM 树,但是它的外部是 #wrapper 元素,因为浏览器会在 Custom Elements 的定义和调用之间添加一个“Shadow DOM 之外”的“干净”的 HTML 标签,以避免在使用自定义元素时出现布局问题。

因此,如果我们想要在 ShadowDOM 中正确呈现自定义元素内部的 DOM 树,我们需要将它们添加到 ShadowDOM 树的根节点。这个根节点可能是一个 <div> 元素、一个 <slot>、一个文本节点,或者是 ShadowDOM 树的本身。这取决于我们为自定义元素指定的 shadowRoot 选项。

下面是正确的写法:

----- --------- ------- ----------- -
  ------------- -
    --------
    ----- ------ - ------------------- ----- ------ ---
    ----- ---- - -------
    ----- ------- - ------------------------------
    ---------- - ----------
    ----------------- - -----------
    --------------------------
  -
-

这里,我们将 shadowRoot 设置为 shadow,并使用它作为自定义元素内部 DOM 树的根节点。现在,自定义元素被正确添加到了 ShadowDOM 中,而且 ::before::after 伪元素也可以正常显示。如下图所示:

Case 2

为了让自定义元素正确应用样式表,我们需要使用“CSS Shadow Parts”(CSS 部件)。CSS 部件是一种将样式表和自定义元素内部的 ShadowDOM 树连接起来的机制,它能够让自定义元素“暴露”出自己的“内部标记”,以便外部样式表可以选择性地影响它们。

暴露内部标记的方法非常简单,只需要在自定义元素的样式表中添加以下内容即可:

------------------------- -
  ------- --- ----- ----
-

这里,我们为自定义元素的 #wrapper 部位添加了一个红色的边框。然后,在我们在定义自定义元素的时候,使用 part 方法对应的部位:

----- --------- ------- ----------- -
  ------------- -
    --------
    ----- ------ - ------------------- ----- ------ ---
    ----- ------- - ------------------------------
    ------------ - ----------
    ----- ------- - ------------------------------
    ---------- - ----------
    ----------------- - -----------
    -----------------------------
    ----------------------------
  -
-

这里,我们给最外层的 <div> 元素 #wrapper 指定了一个 part 名称为 wrapper,然后在样式表中为它添加了样式。现在,我们在浏览器中打开这个页面,就可以看到自定义元素被正确封装在 ShadowDOM 中,并且应用了我们定义的样式表。如下图所示:

结论

在使用 Custom Elements 和 ShadowDOM 的过程中,我们有时会遇到一些奇怪的问题,比如自定义元素没有被正确显示,或者样式表无法应用。这些问题的原因通常很简单,是因为我们对 Custom Elements 和 ShadowDOM 的使用不够熟悉。因此,我们在使用它们的时候,一定要注意一些细节,特别是在处理内部/外部和应用样式表的时候。另外,下面提供了一个完整的示例代码,供大家参考。

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/6734a3130bc820c5824a45cb