在 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