原文地址:developers.google.com/web/fundame…
原文作者:developers.google.com/web/resourc…
发布时间:
TL;DR
通过Custom Elements,web开发者可以创建新的HTML标签,加强现有的HTML标签,或者扩展其他开发者编写的组件。API是网络组件的基础。它带来了一种基于web标准的方式,以创建可重用的组件,而使用的只是普通的JS/HTML/CSS。其结果是在我们的应用程序中减少代码、模块化代码和更多的重用。
介紹
注:本文介绍了新的自定义元素规范。如果您一直在使用自定义元素,您有可能熟悉Chrome 33中的0版本。其概念是相同的,但第1版规范有重要的API差异。继续阅读以了解新内容,或查看历史和浏览器支持部分以了解更多信息。
浏览器为我们提供了一个优秀的工具来构建网络应用。它叫做HTML。你可能已经听说过它了! 它是声明式的,可移植的,支持良好的,并且易于使用。尽管HTML很伟大,但它的词汇量和可扩展性是有限的。HTML生活标准一直缺乏一种将JS行为与您的标记自动关联的方法......直到现在。
自定义元素是实现HTML现代化、填补缺失部分以及将结构与行为捆绑在一起的答案。如果HTML不能提供问题的解决方案,我们可以创建一个能提供解决方案的自定义元素。自定义元素在保留HTML优点的同时,还能教给浏览器新的技巧。
定义一个新元素
要定义一个新的HTML元素,我们需要JavaScript的力量!
customElements全局用于定义自定义元素,并向浏览器介绍新标签。调用customElements.define(),使用你想创建的标签名称和扩展基础HTMLElement的JavaScript类。
示例--定义一个移动抽屉面板,<app-drawer>。
class AppDrawer extends HTMLElement {...}
window.customElements.define('app-drawer', AppDrawer);
// Or use an anonymous class if you don't want a named constructor in current scope.
window.customElements.define('app-drawer', class extends HTMLElement {...});
使用示例:
<app-drawer></app-drawer>
重要的是要记住,使用自定义元素与使用<div>或任何其他元素没有什么不同。可以在页面上声明实例,在JavaScript中动态创建,可以附加事件监听器等。继续阅读,了解更多的例子。
定义一个元素的JavaScript API
自定义元素的功能是使用ES2015 class定义的,该类扩展了HTMLElement。扩展 HTMLElement 可确保自定义元素继承整个 DOM API,并意味着您添加到该类中的任何属性/方法都会成为元素的 DOM 接口的一部分。本质上,使用该类为您的标记创建一个公共 JavaScript API。
例子 - 定义<app-drawer>的DOM接口。
class AppDrawer extends HTMLElement {
// A getter/setter for an open property.
get open() {
return this.hasAttribute('open');
}
set open(val) {
// Reflect the value of the open property as an HTML attribute.
if (val) {
this.setAttribute('open', '');
} else {
this.removeAttribute('open');
}
this.toggleDrawer();
}
// A getter/setter for a disabled property.
get disabled() {
return this.hasAttribute('disabled');
}
set disabled(val) {
// Reflect the value of the disabled property as an HTML attribute.
if (val) {
this.setAttribute('disabled', '');
} else {
this.removeAttribute('disabled');
}
}
// Can define constructor arguments if you wish.
constructor() {
// If you define a constructor, always call super() first!
// This is specific to CE and required by the spec.
super();
// Setup a click listener on <app-drawer> itself.
this.addEventListener('click', e => {
// Don't toggle the drawer if it's disabled.
if (this.disabled) {
return;
}
this.toggleDrawer();
});
}
toggleDrawer() {
...
}
}
customElements.define('app-drawer', AppDrawer);
customElements.define('app-drawer', AppDrawer).在这个例子中,我们正在创建一个具有open属性、disabled属性和toggleDrawer()方法的抽屉。
在这个例子中,我们正在创建一个抽屉,它有一个打开属性、禁用属性和一个toggleDrawer()方法。它还将属性反映为HTML属性。
自定义元素的一个整洁的特点是,this在类定义里面指的是DOM元素本身,即类的实例。在我们的例子中,this指的是<app-drawer>。这个(😉)就是元素可以给自己附加一个click监听器的方法! 而且你并不局限于事件监听器。整个DOM API都可以在元素代码里面使用。使用this来访问元素的属性,检查它的子代(this.children),查询节点(this.querySelectorAll('.items'))等等。
创建自定义元素的规则
- 自定义元素的名称必须包含破折号(-),所以
<x-tags>、<my-element>和<my-awesome-app>都是有效的名称,而<tabs>和<foo_bar>则不是。这个要求是为了让HTML解析器能够区分自定义元素和常规元素。它还确保了在HTML中添加新标签时的向前兼容性。 - 你不能多次注册同一个标签。试图这样做会抛出一个
DOMException。一旦你告诉浏览器一个新的标签,那就完了。没有收回的余地。 - 自定义元素不能自闭,因为 HTML 只允许少数元素自闭。总是写一个关闭标签(
<app-drawer></app-drawer>)。
自定义元素的反应
自定义元素可以定义特殊的生命周期钩子,用于在其存在的有趣时期运行代码。这些被称为自定义元素反应。
| 名称 | 在以下情况下被调用 |
|---|---|
| 构造函数 | 一个元素的实例被创建或升级。用于初始化状态、设置事件监听器或创建影子 dom。关于在构造函数中可以做什么的限制,请参见规范。 |
| connectedCallback | 每当元素插入DOM时调用。对运行设置代码很有用,比如获取资源或渲染。一般来说,你应该尽量将工作推迟到这个时候。 |
| disconnectedCallback | 每次元素从DOM中移除时调用。在运行清理代码时很有用。 |
| attributeChangedCallback(attrName, oldVal, newVal) | 当一个观察到的属性被添加、删除、更新或替换时调用。当一个元素被解析器创建或升级时,也会调用初始值。注意:只有在observedAttributes属性中列出的属性才会收到这个回调。 |
| adoptedCallback | 自定义元素已经被移动到一个新的文档中(例如有人调用document.adopNode(el))。 |
注意:浏览器对observedAttributes数组中列出的任何属性都会调用attributeChangedCallback()(参见观察属性的变化)。本质上,这是一种性能优化。当用户改变一个常见的属性,如样式或类,你不希望被大量的回调淹没。
反应回调是同步的。如果有人在你的元素上调用el.setAttribute(),浏览器会立即调用attributeeChangedCallback()。同样,当你的元素从DOM中移除后,你会立即收到一个disconnectedCallback()(例如,用户调用el.remove())。
例子:在<app-drawer>中添加自定义元素反应。
class AppDrawer extends HTMLElement {
constructor() {
super(); // always call super() first in the constructor.
...
}
connectedCallback() {
...
}
disconnectedCallback() {
...
}
attributeChangedCallback(attrName, oldVal, newVal) {
...
}
}
如果/当它做出感应时,定义反应。如果你的元素足够复杂,并且在connectedCallback()中打开了与IndexedDB的连接,那么在disconnectedCallback()中做必要的清理工作。但是要小心! 你不能依赖你的元素在所有情况下都被从DOM中移除。例如,如果用户关闭了标签页,disconnectedCallback()将永远不会被调用。
属性和特性
将属性反映到属性
HTML属性的值以HTML属性的形式反映到DOM中是很常见的。例如,当在JS中改变hidden或id的值时,可以用以下方法来实现。
div.id = 'my-id';
div.hidden = true;
这些值会作为属性应用到实时DOM中。
<div id="my-id" hidden>
这就是所谓的 "将属性反映到属性"。HTML中几乎所有的属性都是这样做的。为什么这么做呢?属性对于声明性地配置一个元素也很有用,而且某些API(如可访问性和CSS选择器)也依赖于属性来工作。
反映一个属性在任何你想让元素的DOM表示与其JavaScript状态保持同步的地方都很有用。你可能想要反映一个属性的一个原因是,当JS状态改变时,用户定义的样式就会适用。
回想一下我们的<app-drawer>。这个组件的消费者可能希望在它被禁用时淡出它和/或阻止用户交互。
app-drawer[disabled] {
opacity: 0.5;
pointer-events: none;
}
当JS中的disabled属性被改变时,我们希望该属性被添加到DOM中,以便用户的选择器匹配。元素可以通过将值反映到一个同名的属性来提供这种行为。
get disabled() {
return this.hasAttribute('disabled');
}
set disabled(val) {
// Reflect the value of `disabled` as an attribute.
if (val) {
this.setAttribute('disabled', '');
} else {
this.removeAttribute('disabled');
}
this.toggleDrawer();
}
观察属性的变化
HTML属性是一种方便用户声明初始状态的方式。
<app-drawer open disabled></app-drawer>
元素可以通过定义一个attributeChangedCallback对属性变化做出反应。浏览器将为observedAttributes数组中列出的每一个属性变化调用这个方法。
class AppDrawer extends HTMLElement {
...
static get observedAttributes() {
return ['disabled', 'open'];
}
get disabled() {
return this.hasAttribute('disabled');
}
set disabled(val) {
if (val) {
this.setAttribute('disabled', '');
} else {
this.removeAttribute('disabled');
}
}
// Only called for the disabled and open attributes due to observedAttributes
attributeChangedCallback(name, oldValue, newValue) {
// When the drawer is disabled, update keyboard/screen reader behavior.
if (this.disabled) {
this.setAttribute('tabindex', '-1');
this.setAttribute('aria-disabled', 'true');
} else {
this.setAttribute('tabindex', '0');
this.setAttribute('aria-disabled', 'false');
}
// TODO: also react to the open attribute changing.
}
}
在这个例子中,当一个disabled被改变时,我们将在<app-drawer>上设置额外的属性。虽然我们在这里没有这样做,但你也可以使用attributeeChangedCallback来保持一个JS属性与其属性的同步。
元素升级
逐步增强的HTML
我们已经了解到,自定义元素是通过调用customElements.define()来定义的,但这并不意味着你必须一次性定义+注册一个自定义元素。但这并不意味着你必须一次性定义+注册一个自定义元素。
自定义元素可以在其定义注册之前就被使用。
渐进式增强是自定义元素的一个特点。换句话说,你可以在页面上声明一堆<app-drawer>元素,直到很久以后才调用customElements.define('app-drawer',...)。这是因为由于未知的标签,浏览器对潜在的自定义元素的处理方式不同。调用define()并为现有元素赋予类定义的过程称为 "元素升级"。
要知道一个标签名何时成为定义,可以使用 window.customElements.whenDefined()。它返回一个 Promise,解析元素何时成为定义。
customElements.whenDefined('app-drawer').then(() => {
console.log('app-drawer defined');
});
例子--延迟工作,直到一组子元素升级为止
<share-buttons>
<social-button type="twitter"><a href="...">Twitter</a></social-button>
<social-button type="fb"><a href="...">Facebook</a></social-button>
<social-button type="plus"><a href="...">G+</a></social-button>
</share-buttons>
// Fetch all the children of <share-buttons> that are not defined yet.
let undefinedButtons = buttons.querySelectorAll(':not(:defined)');
let promises = [...undefinedButtons].map(socialButton => {
return customElements.whenDefined(socialButton.localName);
});
// Wait for all the social-buttons to be upgraded.
Promise.all(promises).then(() => {
// All social-button children are ready.
});
注意:我认为自定义元素在被定义之前是处于一种边缘状态。规范将一个元素的状态定义为未定义、未自定义或自定义。像
<div>这样的内置元素总是被定义的。
元素定义的内容
自定义元素可以通过使用元素代码里面的DOM API来管理自己的内容。Reactions在这方面很方便。
例子 - 创建一个带有默认HTML的元素。
customElements.define('x-foo-with-markup', class extends HTMLElement {
connectedCallback() {
this.innerHTML = "<b>I'm an x-foo-with-markup!</b>";
}
...
});
声明这个标签会产生
<x-foo-with-markup>
<b>I'm an x-foo-with-markup!</b>
</x-foo-with-markup>
/web/fundamentals/web-components/customelements_ed8e33d648f16398ad880f444090eb59f40c8a9c34fc4c0c1b81bdd86a8ffe15.frame
注意:用新的内容覆盖一个元素的子元素通常不是一个好主意,因为它出乎意料。用户会惊讶于自己的标记被扔掉。添加元素定义内容的一个更好的方法是使用影子DOM,我们接下来会讲到。
创建一个使用影子DOM的元素
注:本文不打算介绍Shadow DOM的功能,但它是一个强大的API,可以与自定义元素相结合。就其本身而言,Shadow DOM是一个构图工具。当它与自定义元素结合使用时,神奇的事情就会发生。
影子DOM为元素提供了一种拥有、渲染和样式DOM的方式,它与页面的其他部分是分开的。Heck,你甚至可以在一个标签中隐藏掉整个应用程序。
<!-- chat-app's implementation details are hidden away in Shadow DOM. -->
<chat-app></chat-app>
要在自定义元素中使用Shadow DOM,在你的构造函数中调用this.attachShadow。
let tmpl = document.createElement('template');
tmpl.innerHTML = `
<style>:host { ... }</style> <!-- look ma, scoped styles -->
<b>I'm in shadow dom!</b>
<slot></slot>
`;
customElements.define('x-foo-shadowdom', class extends HTMLElement {
constructor() {
super(); // always call super() first in the constructor.
// Attach a shadow root to the element.
let shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(tmpl.content.cloneNode(true));
}
...
});
注意:在上面的片段中,我们使用
模板元素来克隆DOM,而不是设置shadowRoot的innerHTML。这种技术减少了HTML的解析成本,因为模板的内容只被解析一次,而对shadowRoot调用innerHTML会对每个实例的HTML进行解析。我们将在下一节中详细讨论模板。
使用示例。
<x-foo-shadowdom>
<p><b>User's</b> custom text</p>
</x-foo-shadowdom>
<!-- renders as -->
<x-foo-shadowdom>
#shadow-root
<b>I'm in shadow dom!</b>
<slot></slot> <!-- slotted content appears here -->
</x-foo-shadowdom>
/web/fundamentals/web-components/customelements_2aaa69f2555be07246309bb756a265f8d67e915c7eaa2b1ee0d739ffad53aeca.frame
从<template>中创建元素
对于那些不熟悉的人来说,<template>元素允许你声明DOM的片段,这些片段被解析,在页面加载时是惰性的,并且可以在以后的运行时被激活。这是web组件家族中的另一个API基元。模板是声明自定义元素结构的理想占位符。
例如:用从<template>创建的Shadow DOM内容注册一个元素。
<template id="x-foo-from-template">
<style>
p { color: green; }
</style>
<p>I'm in Shadow DOM. My markup was stamped from a <template>.</p>
</template>
<script>
let tmpl = document.querySelector('#x-foo-from-template');
// If your code is inside of an HTML Import you'll need to change the above line to:
// let tmpl = document.currentScript.ownerDocument.querySelector('#x-foo-from-template');
customElements.define('x-foo-from-template', class extends HTMLElement {
constructor() {
super(); // always call super() first in the constructor.
let shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(tmpl.content.cloneNode(true));
}
...
});
</script>
这几行代码包罗万象。让我们来了解一下其中的关键内容。
- 我们在HTML中定义了一个新元素。
<x-foo-from-template>。 - 元素的Shadow DOM是由
<template>创建的。 - 由于影子DOM的存在,该元素的DOM是本地的。
- 由于影子DOM的存在,元素的内部CSS的范围被扩大到了元素上
/web/fundamentals/web-components/customelements_1a4d7cf1ffd996533e3d719bc84a3508fde172cdb1a2c835893fb54fdf850cd3.frame
自定义元素的样式
即使你的元素使用Shadow DOM定义了自己的样式,用户也可以在他们的页面上为你的自定义元素设置样式。这些被称为 "用户自定义样式"。
<!-- user-defined styling -->
<style>
app-drawer {
display: flex;
}
panel-item {
transition: opacity 400ms ease-in-out;
opacity: 0.3;
flex: 1;
text-align: center;
border-radius: 50%;
}
panel-item:hover {
opacity: 1.0;
background: rgb(255, 0, 255);
color: white;
}
app-panel > panel-item {
padding: 5px;
list-style: none;
margin: 0 7px;
}
</style>
<app-drawer>
<panel-item>Do</panel-item>
<panel-item>Re</panel-item>
<panel-item>Mi</panel-item>
</app-drawer>
你可能会问自己,如果元素在Shadow DOM中定义了样式,CSS的特异性是如何工作的。在特异性方面,用户样式是赢家。它们总是覆盖元素定义的样式。参见创建使用Shadow DOM的元素一节。
未注册元素的预样式
在元素升级之前,你可以在CSS中使用:defined的伪类来锁定它。这对于预设组件的样式很有用。例如,您可能希望通过隐藏未定义的组件,并在它们被定义后淡出,来防止布局或其他视觉FOUC。
例子 - 在<app-drawer>被定义之前隐藏它。
app-drawer:not(:defined) {
/* Pre-style, give layout, replicate app-drawer's eventual styles, etc. */
display: inline-block;
height: 100vh;
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
当<app-drawer>被定义后,选择器(app-drawer:not(:defined))不再匹配。
扩展元素
自定义元素API对于创建新的HTML元素很有用,但对于扩展其他自定义元素甚至浏览器的内置HTML也很有用。
扩展自定义元素
扩展另一个自定义元素是通过扩展其类定义来完成的。
例子 - 创建<fancy-app-drawer>来扩展<app-drawer>。
class FancyDrawer extends AppDrawer {
constructor() {
super(); // always call super() first in the constructor. This also calls the extended class' constructor.
...
}
toggleDrawer() {
// Possibly different toggle implementation?
// Use ES2015 if you need to call the parent method.
// super.toggleDrawer()
}
anotherMethod() {
...
}
}
customElements.define('fancy-app-drawer', FancyDrawer);
扩展本地HTML元素
假设你想创建一个更高级的<button>。与其复制<button>的行为和功能,一个更好的选择是使用自定义元素逐步增强现有元素。
自定义的内置元素是一种自定义元素,它扩展了浏览器内置的一个HTML标签。扩展现有元素的主要好处是获得其所有功能(DOM属性、方法、可访问性)。没有什么比逐步增强现有的HTML元素更好的方式来编写一个渐进式的Web应用了。
注意:现在只有Chrome 67支持自定义内置元素(状态)。Edge和Firefox将实现它,但Safari选择不实现它。这对于可访问性和渐进式增强来说是不幸的。如果你认为扩展原生HTML元素是有用的,请在Github上发表你对509和662的看法。
要扩展一个元素,你需要创建一个继承自正确DOM接口的类定义。例如,一个扩展<button>的自定义元素需要继承HTMLButtonElement而不是HTMLElement。同样,扩展<img>的元素需要扩展HTMLImageElement。
示例--扩展<button>。
// See https://html.spec.whatwg.org/multipage/indices.html#element-interfaces
// for the list of other DOM interfaces.
class FancyButton extends HTMLButtonElement {
constructor() {
super(); // always call super() first in the constructor.
this.addEventListener('click', e => this.drawRipple(e.offsetX, e.offsetY));
}
// Material design ripple animation.
drawRipple(x, y) {
let div = document.createElement('div');
div.classList.add('ripple');
this.appendChild(div);
div.style.top = `${y - div.clientHeight/2}px`;
div.style.left = `${x - div.clientWidth/2}px`;
div.style.backgroundColor = 'currentColor';
div.classList.add('run');
div.addEventListener('transitionend', e => div.remove());
}
}
customElements.define('fancy-button', FancyButton, {extends: 'button'});
请注意,当扩展一个本地元素时,对 define() 的调用略有变化。所需的第三个参数告诉浏览器你正在扩展哪个标签。这是必要的,因为许多HTML标签共享相同的DOM接口。<section>、<address>和<em>(以及其他)都共享HTMLElement;<q>和<blockquote>都共享HTMLQuoteElement;等等。指定{extends: 'blockquote'}可以让浏览器知道您正在创建一个改进的<blockquote>而不是<q>。请参阅HTML规范,了解HTML的DOM接口的完整列表。
注意:扩展 HTMLButtonElement 可以让我们的漂亮按钮拥有
<button>的所有 DOM 属性/方法。这样我们就不必自己去实现很多东西:disabled属性、**click()**方法、keydown监听器、tabindex管理。相反,我们的关注点可以通过自定义功能逐步增强<button>,即 drawRipple() 方法。更少的代码,更多的重用!
自定义内置元素的消费者可以通过几种方式来使用它。他们可以通过在本地标签上添加is=""属性来声明它。
<!-- This <button> is a fancy button. -->
<button is="fancy-button" disabled>Fancy button!</button>
在JavaScript中创建一个实例。
// Custom elements overload createElement() to support the is="" attribute.
let button = document.createElement('button', {is: 'fancy-button'});
button.textContent = 'Fancy button!';
button.disabled = true;
document.body.appendChild(button);
或者使用new操作符。
let button = new FancyButton();
button.textContent = 'Fancy button!';
button.disabled = true;
下面是另一个扩展<img>的例子。
例子--扩展<img>。
customElements.define('bigger-img', class extends Image {
// Give img default size if users don't specify.
constructor(width=50, height=50) {
super(width * 10, height * 10);
}
}, {extends: 'img'});
用户将该组件声明为
<!-- This <img> is a bigger img. -->
<img is="bigger-img" width="15" height="20">
或在JavaScript中创建一个实例。
const BiggerImage = customElements.get('bigger-img');
const image = new BiggerImage(15, 20); // pass constructor values like so.
console.assert(image.width === 150);
console.assert(image.height === 200);
其他细节
未知元素与未定义的自定义元素
HTML是宽松灵活的工作方式。例如,在页面上声明<randomtagthatdoesntexist>,浏览器完全可以欣然接受。为什么非标准标签可以工作?答案是HTML规范允许的。规范中没有定义的元素会被解析为HTMLUnknownElement。
对于自定义元素来说,情况就不一样了。如果潜在的自定义元素是以有效的名称(包括"-")创建的,则会被解析为HTMLElement。你可以在支持自定义元素的浏览器中进行检查。启动控制台。按Ctrl+Shift+J(或Mac上按Cmd+Opt+J),然后粘贴以下代码。
// "tabs" is not a valid custom element name
document.createElement('tabs') instanceof HTMLUnknownElement === true
// "x-tabs" is a valid custom element name
document.createElement('x-tabs') instanceof HTMLElement === true
API参考
customElements 全局定义了用于处理自定义元素的有用方法。
define(tagName, constructor, options)
在浏览器中定义一个新的自定义元素。
示例:
customElements.define('my-app', class extends HTMLElement { ... });
customElements.define(
'fancy-button', class extends HTMLButtonElement { ... }, {extends: 'button'});
get(tagName)
给定一个有效的自定义元素标签名,返回元素的构造函数。如果没有注册元素定义,则返回undefined。
示例:
let Drawer = customElements.get('app-drawer');
let drawer = new Drawer();
whenDefined(tagName)
返回一个Promise,当自定义元素被定义时,它将被解析。如果元素已经被定义,则立即解析。如果标签名不是有效的自定义元素名,则拒绝。
示例:
customElements.whenDefined('app-drawer').then(() => {
console.log('ready!');
});
历史和浏览器支持
如果你在过去几年里一直在关注Web组件,你会知道Chrome 36+实现了一个版本的自定义元素API,使用document.registerElement()代替customElements.define()。customElements.define()是新的热点,也是浏览器厂商开始实现的。它叫做Custom Elements v1。
如果您碰巧对旧版 v0 规范感兴趣,请查看 html5rocks 文章。
浏览器支持
Chrome 54(状态)、Safari 10.1(状态)和Firefox 63(状态)有自定义元素v1.Edge已经开始开发。
要对自定义元素进行功能检测,请检查 window.customElements 的存在。
const supportsCustomElementsV1 = 'customElements' in window;
Polyfill
在浏览器广泛支持之前,Custom Elements v1有一个独立的polyfill可用。然而,我们建议使用webcomponents.js加载器来优化加载web组件polyfills。该加载器使用特征检测来异步加载浏览器所需的polyfills。
注意:如果您的项目移植到ES5或使用ES5,请务必查看关于除了polyfills之外还包括custom-elements-es5-adapter.js的说明。
安装它。
npm install --save @webcomponents/webcomponentsjs
使用方法:
<!-- Use the custom element on the page. -->
<my-element></my-element>
<!-- Load polyfills; note that "loader" will load these async -->
<script src="node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js" defer></script>
<!-- Load a custom element definitions in `waitFor` and return a promise -->
<script type="module">
function loadScript(src) {
return new Promise(function(resolve, reject) {
const script = document.createElement('script');
script.src = src;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
WebComponents.waitFor(() => {
// At this point we are guaranteed that all required polyfills have
// loaded, and can use web components APIs.
// Next, load element definitions that call `customElements.define`.
// Note: returning a promise causes the custom elements
// polyfill to wait until all definitions are loaded and then upgrade
// the document in one batch, for better performance.
return loadScript('my-element.js');
});
</script>
注意:
:defined的CSS伪类不能被polyfill。
结束语
自定义元素为我们提供了一个新的工具,用于在浏览器中定义新的HTML标签和创建可重用的组件。将它们与其他新的平台基元(如Shadow DOM和<template>)结合起来,我们就可以开始实现Web Components的宏伟图景。
- 跨浏览器(网络标准),用于创建和扩展可重用的组件。
- 不需要任何库或框架就可以开始使用。Vanilla JS/HTML FTW!
- 提供了一个熟悉的编程模型。它只是DOM/CSS/HTML。
- 能很好地与其他新的网络平台功能(影子DOM、
<template>、CSS自定义属性等)配合使用。 - 与浏览器的DevTools紧密集成。
- 充分利用现有的可访问性功能。
通过www.DeepL.com/Translator(免费版)翻译