HTML——Web Components开发

Web Components开发

原生Web Components组件是组件开发的王道实现,首先,浏览器原生支持,性能高;其次,原生语言、跨框架,Vue和React等项目均适用;最后,浏览器已支持多年,社区实践丰富,无须担心兼容性问题和维护成本。

虽然Web Components开发的主力是JavaScript,但是HTML也起到了至关重要的作用。

1、自定义元素和未知元素

在HTML中,元素共分为三类:原生元素、自定义元素和未知元素。其中,原生元素指前文提到的<div>、<span>等诸多元素,自定义元素稍后介绍,这里讲一下未知元素。

未知元素有专门的Web API接口名称,名为HTMLUnknownElement。在网页中,随便写一个标签,例如:

这里的<username>就是一个HTMLUnknownElement元素。简单测试如下:

在HTML规范中,HTMLUnknownElement元素是一个被认可的合法元素,CSS可以无障碍使用,例如:

此时显示的文字效果是ZHANGXINXU。

由于HTMLUnknownElement继承了HTMLElement中的方法,因此,基本上,常用的HTML方法它都是可以畅快使用的。例如,想将文字变红色,可以直接设置:

1.1、不同之处

HTMLUnknownElement和HTMLDivElement、HTMLSpanElement等类型是平级的,都是HTMLElement的子集,区别在于,原生HTML元素自带若干特殊的属性或者方法。例如,表单元素HTMLFormElement有reset()方法、novalidate属性,而HTMLUnknownElement没有携带任何属性和方法。

这其实是一件好事,我们就可以为HTMLUnknownElement扩展非常私有的方法,而不用担心会影响其他元素。例如,默认所有HTMLUnknownElement元素的display计算值都是inline,我们可以扩展block()方法使其块状化。

此时,执行document.querySelector('username').block()就可以让<username>元素块状化了。

1.2、自定义元素

如果我们自定义元素的标签名使用短横线分隔,那么这个元素就不再是未知元素。例如:

在浏览器中的运行结果如图所示。

不过标签是短横线连接的元素也不是真正的自定义元素,需要注册才行,例如:

此时,<user-name>元素就是一个自定义元素了,可以使用document.createElement('user-name')创建,也可以直接在HTML中使用,例如:

1.3、自定义元素分类

自定义元素分为两种:一种是自主自定义元素,比如前面讲的<user-name>元素;另一种是内置自定义元素,是基于标准HTML元素拓展而来的,使用HTML is属性进行标识。

例如,给<ul>元素进行某种自定义扩展:

此时,只需要给<ul>元素设置is="expanding-list",就可以让该元素变成内置自定义元素。

Web Components开发其实都是围绕上述两种自定义元素来展开的,当然,深入到具体细节还有很多其他的知识,下面开始一一介绍。

2、详细了解<template>元素

先来了解一下<template>元素,顾名思义,就是存储HTML模板的元素。

纵观Web发展史,HTML模板总共有四种类型,包括:

随着IE浏览器退出历史舞台,<template>元素可以说是目前HTML模板唯一的选择,因为其具有以下一些优势。

  • 其一,天然隐藏。也就是<template>元素的计算值默认就是none,无须对HTML元素进行额外的设置。
  • 其二,位置任意。类似<script>或者<style>标签,可以设置在<head>中,也可以设置在<body>或者<frameset>中。
  • 其三,非字符串类型。虽然<template>元素的内容可以采用字符串的方式获取,例如:

    但本质上,<template>元素里面的HTML内容还是HTML节点,是可以通过特定的JavaScript API获取的。例如,访问模板中的图片节点就可以这样设置:

    注意,只能使用template.content.querySelector去获取模板内的DOM节点,不能使用template.querySelector或者template.childNodes去获取,因为<template>元素不同于普通的标签元素,其拥有一个独立的document文档,这个特性多少有点类似于<iframe>元素。例如:

    输出的是一个DocumentFragment对象,如图所示。

    因此,我们也可以通过复制节点的方式提取所有的模板内容,就像下面这样:
  • 其四,应用场景广泛。虽然<template>元素是Web Components开发中非常常用的HTML元素之一,但并不意味着<template>元素只能用在Web Components开发中(更多的是指Shadow DOM开发)。在组件之外,只要想存储模板信息,或者希望某些元素延时渲染,都可以使用<template>元素,Vue就是使用<template>元素存储渲染数据的。

3、深入介绍<slot>元素

<slot>元素也被称为插槽元素,是Web Components开发中极为重要的HTML元素,可以实现从外面传入HTML内容的功能。例如,我们自定义一个名为<zxx-alert>的弹出框组件,这个组件内置一个确认按钮,但是弹出的内容却是不固定的,需要从外面传入,此时,就需要用到slot属性和<slot>元素。

其中,slot属性用在组件元素中,<slot>元素则必须位于组件的Shadow DOM元素中。

3.1、了解Shadow DOM

Shadow DOM虽然名义上叫作"DOM"​,实际上它创建了一个独立的文档树,只是这个文档树隐藏在DOM元素中,自成体系,可以不受外部样式的影响。例如,有如下所示的HTML代码:

此时,我们将p#host元素作为Shadow DOM根元素,创建一个按钮元素,如下所示:

此时,页面中的按钮就变成下图所示的样子,可以看到,p#host元素内创建的按钮并未受外面CSS样式的影响。

如果此时打开开发者工具,并且你已经选择了开启显示Shadow DOM,则可以看到下图所示的HTML结构,创建的按钮元素位于一个标记了#shadow-root的结构中。

这个#shadow-root就可以被看成Shadow DOM的根,而其所在的根元素可以使用CSS:host伪类或者:host()函数匹配。

由于Shadow DOM中的CSS和JavaScript通常都不会影响外面的元素,因此,特别适合用于Web Components开发,可以让组件的样式与功能不受外部环境影响。

不过,组件总是需要与外部环境通信的,可能是内容,也可能是属性,还可能是样式,无论是内容、属性,还是样式都有对应的元素或属性支持。例如,内容的交互通信使用<slot>元素。回到一开始提到的自定义提示框案例,想要实现内容占位效果,可以这么处理:首先定义模板,然后动态内容使用<slot>元素占位,例如弹框提示内容和一个按钮元素。

随后,将此HTML模板写入组件元素的Shadow DOM中,就像下面这样:

此时,只要在页面中像普通元素那么使用自定义的<zxx-alert>元素,代码如下所示,就可以看到下图所示的弹出框效果了。

也就是说,只要设置了slot属性的元素,就会自动替换组件Shadow DOM中name属性值和slot属性值一致的<slot>元素,这就是<slot>元素的作用规则。

3.2、<slot>元素的匹配细节

插槽匹配规则很简单,即<slot>插槽元素的name属性值和任意HTML元素的slot属性值一致的时候会被匹配,不过其中有几个细节需要了解一下。

3.2.1、name属性值唯一

<slot>元素支持name属性,可以看成身份标识,需要是唯一的,因为多个相同的name属性最多只会匹配一个插槽元素。例如:

其中name="alert"插槽元素就不会被HTML替换,如图所示,可以看到下面那个占位的<slot>元素的默认文字并未被替换。

3.2.2、slot属性值可以不唯一

slot属性值可以不唯一,例如下面这段HTML代码:

此时会看到两段执行成功的提示,如图所示(截自Firefox浏览器)​。

3.2.3、同一个组件可以使用多个插槽

同一个组件可以使用多个插槽,例如,预留一个按钮的位置:

此时可以看到,不仅提示内容被插入弹框中,取消按钮也一并被插入,效果如图所示。

3.2.4、必须是组件的子元素

必须将用来匹配的元素写在自定义元素组件中,作为子元素存在,例如下面的配对元素在自定义元素之外,是没有插槽效果的:

像下面这种情况,匹配元素不是子元素,而是子元素的子元素,也是无法作为有效的插槽显示的:

效果如图所示,可以看到并没有"取消"按钮。

即使<div>元素设置了display:contents也无效。

3.3、<slot>元素中的事件

以弹框中的"取消"按钮为例,如何给"取消"按钮的插槽添加事件呢?

虽然视觉上匹配元素替换了插槽元素,但实际上两者的位置并未发生变化,其HTML结构关系如图所示。

因此,要实现点击弹框中的"取消"按钮来关闭弹框,只需要在原始的<slot>元素或者匹配的<button>元素上写事件,DOM层级关系依然按照原始的DOM关系处理就可以了。

例如,下面两种处理方式都可以关闭弹框:

1.匹配按钮元素添加事件

2.在定义Web组件的时候处理(只显示关键代码)​:

眼见为实,以上出现的案例效果均可以通过在浏览器中输入https://www.htmlapi.cn/10/1-1.html访问进行体验。

3.4、特殊的display计算值与样式设置

据我所知,<slot>元素是唯一display默认值是contents的元素,如图所示。

display:contents的作用就是其名字所表示的意思,只有内容盒子,取消其余所有盒子,因此,设置display:contents的元素不能参与布局,不能设置边框和背景色,只能设置被动继承颜色和字号等具有继承特性的CSS属性。此display计算值在关键时候还是非常有用的,比方说在标签嵌套过深并影响布局的时候。

::slotted()函数与样式设置

另外,如果希望在Shadow DOM内部对元素进行样式设置,则可以使用CSS::slotted()函数,参数就是占位元素的选择器,例如:

此时,只要组件里面的占位元素是<p>元素,那么出现的文字颜色就是红色加粗的,如图所示。

4、了解part属性和exportparts属性

Shadow DOM树可以被看作一个封闭的小世界,好处是里面的样式和交互不易受外部环境干扰,实现的组件更加稳固,但是总存在需要对组件内部样式自定义的情况,就像原生的单选按钮、复选框元素、<progress>进度元素的样式自定义那样。

为了解决这样的问题,就设计了part属性和exportparts属性,专门将Shadow DOM树里面的元素暴露在外面,通过特殊的CSS选择器进行匹配,从而实现样式的自定义。

4.1、更常用的part属性

先来说说part属性,直接看案例,还是以前面出现的<zxx-alert>元素为例。如果希望在业务代码中定义组件内<dialog>元素的样式,就需要在<dialog>元素上使用part属性,例如设置part="dialog",代码如下所示:

此时,若想自定义<dialog>元素的样式,就可以通过CSS::part()函数实现,只要将此函数的参数设置为对应元素的part属性值即可,例如:

此时,我们打开如下所示的<zxx-alert>元素,看到的就是图所示的样式效果,<dialog>元素默认的黑色边框不见了,代之的是更加舒服的圆角效果。

可以通过在浏览器中输入https://www.htmlapi.cn/10/1-2.html来访问体验上述效果。

4.2、嵌套组件解决方案exportparts属性

exportparts属性的作用也是对外暴露元素的匹配接口,区别在于exportparts属性是设置在组件内的组件元素上的,而part属性是设置在组件内的普通元素上的。例如,我们再自定义一个按钮组件元素,来替换<zxx-alert>组件中的<button>元素,不妨就称之为<zxx-button>元素,相关代码如下所示:

此时,要想对<zxx-button>元素中创建的<button>元素进行样式设置,就需要exportparts属性进行穿透,例如:

于是,在页面中,我们就可以使用::part(button)函数对<zxx-alert>组件中的<button>元素进行样式设置了,例如:

此时,弹出框显示的按钮如图所示,明显好看多了。

可以通过在浏览器中输入https://www.htmlapi.cn/10/1-3.html访问来体验上述效果。

1.支持多属性值

exportparts属性值可以设置为多个,以逗号分隔,例如:

2.支持别称

exportparts属性值还支持别称,使用冒号区分原名和别称,例如:

此时,按钮的样式可以使用::part(btn)函数进行匹配。

5、后起之秀is属性

is属性是一个HTML全局属性,配合JavaScript代码,可以让任意内置的HTML元素变成内置的自定义组件。这个属性不难理解,只需要一个简单的案例即可,还是以<dialog>对话框元素为例,<dialog>元素内置了显示和隐藏的方法,但是很奇怪,这两个方法的名称并不是相对的,弹框显示是dialog.show()方法,弹框隐藏却是dialog.close()方法,很多人在使用的时候会执行错误,所以,是不是可以让<dialog>元素天然支持hide()方法呢?

自然可以,通过一个简单的定义和注册,以及is属性就可以了,完整的代码如下所示:

此时,只要给<dialog>元素设置is="zxx-dialog",此<dialog>元素就可以调用hide()方法了,例如:

此时,点击弹框元素内容的"关闭"按钮,弹框就会隐藏。这就是is属性的作用,可以让内置的HTML原生元素拥有自定义的属性和方法,这是Web Components开发中非常重要的一个分支。不过Safari浏览器对is属性的支持不太友好,然而不影响,引入一段polyfill脚本就可以几乎无障碍地使用is属性的各个特征,但不包括CSS:defined伪类,也就是Safari浏览器的:defined伪类只能匹配自定义组件元素,无法匹配内置自定义元素。

可以通过在浏览器中输入https://www.htmlapi.cn/10/1-4.html访问来体验上述效果。

相关推荐
溪语流沙2 小时前
【每天一个CSS | Day08】整排按钮被鼠标吸走的磁吸导航
前端·css·计算机外设
就叫年华吧丶2 小时前
长文档点目录定位总是不准?一个 content-visibility + 平滑滚动引发的连环坑(Vue3 实战)
前端·javascript·算法·vue
JudithHuang2 小时前
React 路由:React Router
前端·react.js·前端框架
赵得C2 小时前
创建 SVG 图标预览页面:从零实现到解决 CORS 问题
前端·javascript·html
IT_陈寒2 小时前
Java的HashMap线程安全问题让我深夜掉光了头发
前端·人工智能·后端
三掌柜6662 小时前
ArkWeb 手记 04|Web 返回键别再一刀切
前端·harmonyos
光影少年2 小时前
RN与Flutter架构区别
前端·flutter·react native·react.js·架构·node.js
peter67682 小时前
css学习小结
前端·css·学习