window 属性:自定义元素(custom elements)
概述
Web Components 标准非常重要的一个特性是,它使开发者能够将HTML页面的功能封装为 custom elements(自定义标签),而往常,开发者不得不写一大堆冗长、深层嵌套的标签来实现同样的页面功能。这篇文章将会介绍如何使用HTML的custom elements。Firefox、Chrome和Opera默认就支持 custom elements。Safari目前只支持 autonomous custom elements(自主自定义标签),而 Edge也正在积极实现中。
CustomElementRegistry
接口的实例用来处理 web 文档中的 custom elements — 该对象允许你注册一个 custom element,返回已注册 custom elements 的信息,等等。
CustomElementRegistry.define()
方法用来注册一个 custom element,该方法接受以下参数:
- 表示所创建的元素名称的符合
DOMString
标准的字符串。注意,custom element 的名称不能是单个单词,且其中必须要有短横线。 - 用于定义元素行为的 类 。
可选参数
,一个包含extends
属性的配置对象,是可选参数。它指定了所创建的元素继承自哪个内置元素,可以继承任何内置元素。
作为示例,我们可以像这样定义一个叫做 word-count 的 custom element:
1 | customElements.define( 'word-count' , WordCount, { extends : 'p' }); |
这个元素叫做 word-count
,它的类对象是 WordCount
, 继承自 <p>
元素.
一个 custom element 的类对象可以通过 ES 2015 标准里的类语法生成。所以,WordCount
可以写成下面这样:
1 2 3 4 5 6 7 8 9 10 | class WordCount extends HTMLParagraphElement { constructor() { // 必须首先调用 super 方法 super (); // 元素的功能代码写在这里 ... } } |
上面只是一个简单的例子,我们能做的不只这些。在构造函数中,我们可以设定一些生命周期的回调函数,在特定的时间,这些回调函数将会被调用。例如,connectedCallback
会在 custom element首次被插入到文档DOM节点上时被调用,而 attributeChangedCallback
则会在 custom element增加、删除或者修改某个属性时被调用。
你可以在 使用生命周期回调函数段落中了解更多相关信息。
共有两种 custom elements:
- Autonomous custom elements 是独立的元素,它不继承其他内建的HTML元素。你可以直接把它们写成HTML标签的形式,来在页面上使用。例如
<popup-info>
,或者是document.createElement("popup-info")
这样。 - Customized built-in elements 继承自基本的HTML元素。在创建时,你必须指定所需扩展的元素(正如上面例子所示),使用时,需要先写出基本的元素标签,并通过
is
属性指定custom element的名称。例如<p is="word-count">
, 或者document.createElement("p", { is: "word-count" })
。
示例
让我们来看几个简单示例,来了解如何创建 custom elements。
Autonomous custom elements
我们来看一下 <popup-info-box>
(查看在线示例),一个关于 autonomous custom element的例子。它包含有一个图标和一段文字,并且图标显示在页面上。在这个图标获取焦点时,它会显示一个包含该段文字的信息框,用于展示更多的信息。
为了实现这个功能,首先创建一个JavaScript文件,定义一个叫做PopUpInfo
的类,它继承自HTMLElement
。Autonomous custom elements 总是继承自HTMLElement
。
1 2 3 4 5 6 7 8 9 10 | class PopUpInfo extends HTMLElement { constructor() { // 必须首先调用 super方法 super (); // 元素的功能代码写在这里 ... } } |
上述代码片段中,类的构造函数constructor
总是先调用super()
来建立正确的原型链继承关系。
在构造函数中,我们会定义元素实例所拥有的全部功能。作为示例,我们首先会将shadow root附加到custom element上,然后通过一系列DOM操作创建custom element的内部阴影DOM结构,再将其附加到 shadow root上,最后再将一些CSS附加到 shadow root的style节点上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | // 创建一个 shadow root var shadow = this .attachShadow({mode: 'open' }); // 创建一个 spans var wrapper = document.createElement( 'span' ); wrapper.setAttribute( 'class' , 'wrapper' ); var icon = document.createElement( 'span' ); icon.setAttribute( 'class' , 'icon' ); icon.setAttribute( 'tabindex' , 0); var info = document.createElement( 'span' ); info.setAttribute( 'class' , 'info' ); // 获取text属性上的内容,并添加到一个span标签内 var text = this .getAttribute( 'text' ); info.textContent = text; // 插入 icon var imgUrl; if ( this .hasAttribute( 'img' )) { imgUrl = this .getAttribute( 'img' ); } else { imgUrl = 'img/default.png' ; } var img = document.createElement( 'img' ); img.src = imgUrl; icon.appendChild(img); // 创建一些 CSS,并应用到 shadow dom上 var style = document.createElement( 'style' ); style.textContent = '.wrapper {' + // 简洁起见,省略了具体的CSS // 将创建的元素附加到 shadow dom shadow.appendChild(style); shadow.appendChild(wrapper); wrapper.appendChild(icon); wrapper.appendChild(info); |
最后,我们使用之前提到的define()
方法将 custom element注册到CustomElementRegistry
上,在方法的参数里,我们指定了元素的名称,以及定义了元素功能的类。
1 | customElements.define( 'popup-info' , PopUpInfo); |
现在我们可以在页面上使用我们定义的custom element了,就像下面这样:
1 2 3 | < popup-info img="img/alt.png" text="Your card validation code (CVC) is an extra security feature — it is the last 3 or 4 numbers on the back of your card."> |
注意: 上方代码不是最新,你可以在这里找到完整的源码。
译者提示: 在Chrome版本76.0.3809.132(正式版本)(64 位)中测试发现,customElements.define()
必须在js文件中调用,且引用此js文件时必须在script
标签上添加defer
属性,否则this.getAttribute('属性名称')
无法获取到值。
Customized built-in elements
现在让我们来看一下另一个有关customized built in element(自定义内置元素)的示例— expanding-list (查看在线示例)。该示例将所有的无序列表转化为一个可收起/展开的菜单。
首先,我们定义一个元素的类,这和之前一样:
1 2 3 4 5 6 7 8 9 10 | class ExpandingList extends HTMLUListElement { constructor() { // 必须首先调用 super方法 super (); // 元素的功能代码写在这里 ... } } |
在这里,我们不会详细解释元素的功能细节,你可以在源码中了解它的工作方式。这里的真正不同点在于元素继承的是HTMLUListElement
接口,而不是HTMLElement
。所以它拥有<ul>
元素所有的特性,以及在此基础上我们定义的功能,这是与独立元素(standalone element)不同之处。这也是为什么我们称它为 customized built-in元素,而不是一个autonomous元素。
接下来,和之前一样,我们使用define()
方法注册一个元素,但不同的是,我们需要添加一个配置对象,用于指定我们需要继承的元素:
1 | customElements.define( 'expanding-list' , ExpandingList, { extends : "ul" }); |
在页面上使用 built-in element看起来也会有所不同:
1 2 3 4 5 | < ul is="expanding-list"> ... </ ul > |
你可以正常使用<ul>
标签,也可以通过is
属性来指定一个custom element的名称。
注意: 同样的,你可以在这里找到完整的 JavaScript 源码。
译者注:在 chrome 66 版本上,该示例无法正确工作,相关问题:
How to create new instance of an extended class of custom elements
使用生命周期回调函数
在custom element的构造函数中,可以指定多个不同的回调函数,它们将会在元素的不同生命时期被调用:
connectedCallback
:当 custom element首次被插入文档DOM时,被调用。disconnectedCallback
:当 custom element从文档DOM中删除时,被调用。adoptedCallback
:当 custom element被移动到新的文档时,被调用。attributeChangedCallback
: 当 custom element增加、删除、修改自身属性时,被调用。
我们来看一下它们的一下用法示例。下面的代码出自life-cycle-callbacks示例(查看在线示例)。这个简单示例只是生成特定大小、颜色的方块。custom element看起来像下面这样:
1 | < custom-square l="100" c="red"></ custom-square > |
这里,类的构造函数很简单 — 我们将 shadow DOM附加到元素上,然后将一个<div>
元素和<style>
元素附加到 shadow root上:
1 2 3 4 5 6 | var shadow = this .attachShadow({mode: 'open' }); var div = document.createElement( 'div' ); var style = document.createElement( 'style' ); shadow.appendChild(style); shadow.appendChild(div); |
示例中的关键函数是 updateStyle()
—它接受一个元素作为参数,然后获取该元素的shadow root,找到<style>
元素,并添加width
,height
以及background-color
样式。
1 2 3 4 5 6 7 8 9 10 11 12 | function updateStyle(elem) { var shadow = elem.shadowRoot; var childNodes = shadow.childNodes; for ( var i = 0; i < childNodes.length; i++) { if (childNodes[i].nodeName === 'STYLE' ) { childNodes[i].textContent = 'div {' + ' width: ' + elem.getAttribute( 'l' ) + 'px;' + ' height: ' + elem.getAttribute( 'l' ) + 'px;' + ' background-color: ' + elem.getAttribute( 'c' ); } } } |
实际的更新操作是在生命周期的回调函数中处理的,我们在构造函数中设定类这些回调函数。当元素插入到DOM中时,connectedCallback()
函数将会执行 — 在该函数中,我们执行updateStyle()
函数来确保方块按照定义来显示;
1 2 3 4 | connectedCallback() { console.log( 'Custom square element added to page.' ); updateStyle( this ); } |
disconnectedCallback()
和adoptedCallback()
回调函数只是简单地将消息发送到控制台,提示我们元素什么时候从DOM中移除、或者什么时候移动到不同的页面:
1 2 3 4 5 6 7 | disconnectedCallback() { console.log( 'Custom square element removed from page.' ); } adoptedCallback() { console.log( 'Custom square element moved to new page.' ); } |
每当元素的属性变化时,attributeChangedCallback()
回调函数会执行。正如它的属性所示,我们可以查看属性的名称、旧值与新值,以此来对元素属性做单独的操作。在当前的示例中,我们只是再次执行了updateStyle()
函数,以确保方块的样式在元素属性值变化后得以更新:
1 2 3 4 | attributeChangedCallback(name, oldValue, newValue) { console.log( 'Custom square element attributes changed.' ); updateStyle( this ); } |
需要注意的是,如果需要在元素属性变化后,触发 attributeChangedCallback()
回调函数,你必须监听这个属性。这可以通过定义observedAttributes()
get函数来实现,observedAttributes()
函数体内包含一个 return语句,返回一个数组,包含了需要监听的属性名称:
1 | static get observedAttributes() { return [ 'w' , 'l' ]; } |
在我们的例子中,该段代码处于构造函数的上方。
注意: 在这里查看完整的 JavaScript源码。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」