资讯专栏INFORMATION COLUMN

Shadow DOM 内部构造及如何构建独立组件

妤锋シ / 1318人阅读

摘要:此即如何实现局部样式化的原理。这是一个绝佳的方式,开发者可以在组件内部封装响应用户交互或者状态的行为,然后基于宿主元素来样式化内部节点。

原文请查阅这里,略有删减,本文采用知识共享署名 4.0 国际许可协议共享,BY Troland。

这是 JavaScript 工作原理的第十七章。

概述

网页组件指的是允许开发者使用一系列不同的技术来创建可复用的自定义元素,组件内的功能不影响其它代码,以便于开发者在网页程序中使用。

有四种网页组件标准:

Shadow DOM

HTML 模板

自定义元素

HTML Imports

本章主要讨论 Shadow DOM

Shadow DOM 是一个被设计用来构建基于组件(积木式)的网页程序的工具。它为开发者可能经常遇到过的问题提供了解决方案:

隔离的 DOM:组件的 DOM 是独立的(比如 document.querySelector() 无法检索到组件 shadow DOM 下的f元素节点)。这样就可以简化网页程序中的 CSS 选择器,因为 DOM 组件是互不影响,这样就允许开发者可以随心所欲地使用更加通用的 id/class 命名而不用担心命名冲突。

局部样式: shadow DOM 内定义的样式不会污染 shadow DOM 之外的元素。Style 样式规则不会泄漏且页面样式也不会污染 shadow DOM 内的元素样式。

组合:为开发者的组件设计一个声明式,基于标签的接口。

Shadow DOM

本篇文章假设开发者已经对 DOM 及其 API 熟拈于心。否则,可以阅读一下这方面的详细资料。

与一般的 DOM 元素相比,Shadow DOM 有两处不同的地方:

与一般创建和使用 DOM 的方式相比,开发者如何创建及使用 Shadow DOM 及其与页面上的其它元素的关系

其展现形式与页面上的其它元素的关系

一般情况下,开发者创建 DOM 节点,然后将其作为子元素挂载到其它元素下。对于 shadow DOM,开发者创建一个独立 DOM 树挂载到目标元素下而该树和其实际子元素是分离的。该独立子树称为 shadow 树。shadow 树的挂载元素称为 shadow 宿主。包括 Launch 模板

当开发者不得不在网页上复用相同的标记结构的时候,最好使用某种模板而不是重复书写相同的页面结构。以前是可以实现的,但是现在可以使用

可以以如下方式使用刚才使用模板创建的自定义组件:

插槽

模板有一些不足的地方,主要的不足在于静态内容不允许开发者像一般的标准 HTML 模板那样渲染自定义的变量或者数据。

这时候 就派上用场了。

可以把插槽看成是允许开发者在模板中放置自定义 HTML 的占位符的功能。这样开发者就可以创建能用的 HTML 模板并且通过引入插槽来自定义渲染内容。

让我们看一下以上模板添加一个插槽的代码如下:

如果在标记中引用该元素的时候没有定义插槽内容,或者浏览器不支持插槽,则 只会包含默认的 "Default text" 内容。

若想要定义插槽内容,开发者得在 中定义元素 HTML 结构的 slot 属性值和对应填充的插槽名称保持一致即可。

如前所述,开发者可以随便写插槽内容:


 Let"s have some different text!

所有可以被插入插槽的元素被称为可插入元素;已插入插槽元素称为插槽元素。

注意以上示例中插入的 元素即是插槽元素。它拥有一个 slot 属性,属性值和模板中插槽定义的 name 属性值相等。

浏览器渲染之后,以上代码会创建如下扁平 DOM 树:


  #shadow-root
  

Default text

Let"s have some different text!

这里原文有误,有改动。

注意 #shadow-root 元素只是表示存在 Shadow DOM 而已。

样式化

可以在主页面样式化含有 shadow DOM 的组件,可以定义组件样式或者提供 CSS 自定义属性的形式让用户覆盖掉默认样式值。

组件定义的样式

局部样式 是 Shadow DOM 极好的功能之一:

主页面上的 CSS 选择器不会影响到组件内部元素的样式。

组件内部定义的样式不会影响页面上的其它元素样式。它们只作用于宿主元素。

Shadow DOM 中的 CSS 选择器只影响组件内部的元素。实际上,这意味着开发者可以重复使用通用的 id/class 名称而不用担心和主页面上的其它样式发生冲突。简单的 CSS 选择器可以提高页面性能。

让我们看一下如下 #shadow-root 中定义的一些样式:

#shadow-root


以上示例中的样式只会作用于 #shadow-root 内部。

开发者也可以在 #shadow-root 里面使用 元素来引入样式表,也只作用于 #shadow-root 内部。

:host 伪类

:host 伪类允许开发者选择和样式化包含 shadow 树的宿主元素:

只有一个地方需要注意即若主页面上定义的宿主元素样式优先级比元素里面定义的 :host 样式规则要高。这样就允许开发者从外部覆盖掉组件内部定义的顶级样式。

即当在主页面上定义了如下的样式:

my-paragraph {
  marbin-bottom: 40px;
}

同理,:host 只在shadow 根的上下文中起作用,因此开发者不能够在 Shadow DOM 外面使用。

:host() 这样的功能样式允许开发者只样式化匹配 的宿主元素。这是一个绝佳的方式,开发者可以在组件内部封装响应用户交互或者状态的行为,然后基于宿主元素来样式化内部节点。

使用 :host-context() 伪类来定制化元素样式

:host-context() 伪类找出宿主元素或者宿主元素任意的祖先元素匹配

常用于定制化。例如,开发者通过为 或者 添加类来进行定制化:


  

或者

当宿主元素的祖先元素包含有 .lightheme 类 :host-context(.lightheme) 将会样式化

:host-context(.lightheme) {
  color: black;
  background: white;
}

可以使用 :host-context() 来进行定制化主题样式,但是更好的方法即通过 CSS 自定义属性来创建样式钩子。

从外部样式化组件宿主元素

开发者可以从外部通过把标签名作为选择器来样式化组件宿主元素,如下:

custom-container {
  color: red;
}

外部样式比 Shadow DOM 中定义的样式拥有更高的优先级。

例如,假设用户书写如下选择器:

custom-container {
  width: 500px;
}

将会覆盖如下组件样式规则 :

:host {
  width: 300px;
}

组件自身样式化只能做到这么多。但如果想要样式化组件内部属性呢?这就需要 CSS 自定义属性。

使用 CSS 自定义属性来创建样式钩子

若组件作者使用 CSS 自定义属性提供样式钩子,用户可以用来更改内部样式。

这和 思路类似只是应用到了样式。

让我们看如下示例:




Shadow DOM 内部:

:host([background]) {
  background: var(--custom-container-bg, #CECECE);
  border-radius: 10px;
  padding: 10px;
}

该示例中,因为用户提供了该背景颜色值,所以组件将会把黑色作为背景颜色值。否则,默认为 #CECECE

作为组件作者,需要让开发者知道可以使用的 CSS 自定义属性。可以把自定义属性看作组件的公共接口。

插槽 JavaScript 接口

Shadow DOM API 可能用来操作插槽。

slotchange 事件

当一个插槽的分发元素节点发生变化的时候触发 slotchange 事件。例如,当用户从 light DOM 中添加/删除子节点。

var slot = this.shadowRoot.querySelector("#some_slot");
slot.addEventListener("slotchange", function(e) {
  console.log("Light DOM change");
});

可以在元素的构造函数中创建 MutationObserver 来监听 light DOM 的其它类型的修改事件。前面文章中有介绍过 MutationObserver 的内部构造及使用指南。

assignedNodes() 方法

了解哪些元素是和插槽有关是很有用处的。调用 slot.assignedNodes() 可以找出哪些元素是由插槽渲染的。flatten: true} 选项会返回插槽的默认内容(若没有分发任何节点)。

看一下如下示例:

Default content

假设以上内容包含在一个叫做 的组件内部。

让我们查看一下该组件的不同用法,然后调用 assignedNodes() 输出不同的结果:

第一例中,我们将往插槽中添加内容:


   container text 

调用 assignedNodes() 将会返回 [ container text ]。注意结果为一个节点数组。

第二例中,将不添加内容:

调用 assignedNodes() 将会返回空数组 []

但是,假设添加 {flatten: true} 参数将会返回默认内容:[

Default content

]

同理,为了查找插槽中的元素,开发者可以调用 assignedNodes() 来找出元素被挂载到哪个组件插槽中。

事件模型

Shadow DOM 中的事件冒泡的经过是值得注意的。

事件目标被调整为维护 Shadow DOM 的封闭性。当事件被重新定位,看起来是由组件自身产生而不是组件的 Shadow DOM 内部元素。

这里有传播出 Shadow DOM 的事件列表(还有一些只能在 Shadow DOM 内传播):

Focus 事件:blur, focus, focusin, focusout

鼠标事件:click, dblclick, mousedown, mouseenter, mousemove 等.

滚轮事件: wheel

输入事件: beforeinput, input

键盘事件: keydown, keyup

组合事件: compositionstart, compositionupdate, compositionend

拖拽事件: dragstart, drag, dragend, drop 等.

自定义事件

默认情况下,自定义事件不会传播出 Shadow DOM。开发者若想要分派自定义事件且想要传播出 Shadow DOM,需要添加 bubbles: truecomposed: true 选项参数。

让我们瞧瞧类似这样的事件分派:

var container = this.shadowRoot.querySelector("#container");
container.dispatchEvent(new Event("containerchanged", {bubbles: true, composed: true}));
浏览器兼容情况

可以通过检查 attachShadow 来检查是否支持 Shadow DOM 功能:

const supportsShadowDOMV1 = !!HTMLElement.prototype.attachShadow;

参考资料:

https://developer.mozilla.org...

https://developers.google.com...

https://developer.mozilla.org...

https://www.html5rocks.com/en...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/102259.html

相关文章

  • JavaScript 是如何工作: Shadow DOM内部结构+如何编写独立组件

    摘要:向影子树添加的任何内容都将成为宿主元素的本地元素,包括,这就是影子实现样式作用域的方式。 这是专门探索 JavaScript 及其所构建的组件的系列文章的第 17 篇。 想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你! 如果你错过了前面的章节,可以在这里找到它们: JavaScript 是如何工作的:引擎,运行时和调用堆栈的概述! JavaScript 是如何工作...

    godlong_X 评论0 收藏0
  • 纯原生组件化-模块化的探索

    摘要:纯原生的组件化模块化的一次小小的尝试,用到了如下几个新特性对标签结构的一个封装,真正意义上的组件,能保证中的元素不会被外界影响,内部也不会影响到外部的行为,变成了一个独立的模块。 纯原生的组件化、模块化的一次小小的尝试,用到了如下几个新特性:shadown-DOM 对HTML标签结构的一个封装,真正意义上的组件,能保证 shadow-DOM 中的DOM元素不会被外界影响,内部也不会影响...

    张金宝 评论0 收藏0
  • 自定义元素探秘构建可复用组件最佳实践

    摘要:若自定义元素标签名称不可用则摒弃。总之,自定义元素让开发者的代码更易理解和维护,并分割为小型,可复用及可封装的模块。被称为自定义元素接口,虽然现在仍然可用,但是已经被弃用并被认为是糟糕的实现。 原文请查阅这里,略有删减,本文采用知识共享署名 4.0 国际许可协议共享,BY Troland。 这是 JavaScript 工作原理第十九章。 概述 在 前述文章中,我们介绍了 Shadow ...

    CoorChice 评论0 收藏0
  • # Web Components 全揽

    摘要:定制元素可以在原生元素外创建定制元素。此定制元素内部有一个加号按钮,一个减号按钮,一个显示当前值。此主题会在下一部分内介绍。定制元素的属性元素的属性被称为,对象内的属性被称为。做响应的同步处理。 Web Components 全揽 Web Components技术可以把一组相关的HTML、JS代码和CSS风格打包成为一个自包含的组件,只要使用大家熟悉的标签即可引入此组件。Web Com...

    legendmohe 评论0 收藏0

发表评论

0条评论

妤锋シ

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<