自定义元素的类型

有两种类型的自定义元素:

自定义内置元素(Customized built-in element)继承自标准的 HTML 元素,例如 HTMLImageElement 或 HTMLParagraphElement。它们的实现定义了标准元素的行为。

独立自定义元素(Autonomous custom element)继承自 HTML 元素基类 HTMLElement。你必须从头开始实现它们的行为。

实现自定义元素

自定义元素作为一个类来实现,该类可以扩展 HTMLElement(在独立元素的情况下)或者你想要定制的接口(在自定义内置元素的情况下)。

以下是一个最小自定义元素的实现示例,该元素定制了

元素:

jsclass WordCount extends HTMLParagraphElement {

constructor() {

super();

}

// 此处编写元素功能

}

以下是一个独立自定义元素的最小实现:

jsclass PopupInfo extends HTMLElement {

constructor() {

super();

}

// 此处编写元素功能

}

在类的构造函数中,你可以设置初始状态和默认值,注册事件监听器,甚至创建一个影子根(shadow root)。在此处,你不应检查元素的属性或子元素,也不应添加新的属性或子元素。有关完整的要求集,请参阅自定义元素构造函数和交互行为的要求。

自定义元素生命周期回调

一旦你的自定义元素被注册,当页面中的代码以特定方式与你的自定义元素交互时,浏览器将调用你的类的某些方法。通过提供这些方法的实现,规范称之为生命周期回调,你可以运行代码来响应这些事件。

自定义元素生命周期回调包括:

connectedCallback():每当元素添加到文档中时调用。规范建议开发人员尽可能在此回调中实现自定义元素的设定,而不是在构造函数中实现。

disconnectedCallback():每当元素从文档中移除时调用。

adoptedCallback():每当元素被移动到新文档中时调用。

attributeChangedCallback():在属性更改、添加、移除或替换时调用。有关此回调的更多详细信息,请参见响应属性变化。

以下是一个记录这些生命周期事件的最小自定义元素示例:

js// 为这个元素创建类

class MyCustomElement extends HTMLElement {

static observedAttributes = ["color", "size"];

constructor() {

// 必须首先调用 super 方法

super();

}

connectedCallback() {

console.log("自定义元素添加至页面。");

}

disconnectedCallback() {

console.log("自定义元素从页面中移除。");

}

adoptedCallback() {

console.log("自定义元素移动至新页面。");

}

attributeChangedCallback(name, oldValue, newValue) {

console.log(`属性 ${name} 已变更。`);

}

}

customElements.define("my-custom-element", MyCustomElement);

注册自定义元素

要使自定义元素在页面中可用,请调用 Window.customElements 的 define() 方法。

define() 方法接受以下参数:

name

元素的名称。必须以小写字母开头,包含一个连字符,并符合规范中有效名称的定义中列出的一些其他规则。

constructor

自定义元素的构造函数。

options

仅对于自定义内置元素,这是一个包含单个属性 extends 的对象,该属性是一个字符串,命名了要扩展的内置元素。

例如,以下代码注册了名为 WordCount 的自定义内置元素:

jscustomElements.define("word-count", WordCount, { extends: "p" });

以下代码注册了名为 PopupInfo 的独立自定义元素:

jscustomElements.define("popup-info", PopupInfo);

使用自定义元素

一旦你定义并注册了自定义元素,就可以在代码中使用它。

要使用自定义内置元素,请使用内置元素,但将自定义名称作为 is 属性的值:

html

要使用独立自定义元素,就像使用内置的 HTML 元素一样,使用自定义名称即可:

html

响应属性变化

与内置元素一样,自定义元素可以使用 HTML 属性来配置元素的行为。为了有效地使用属性,元素必须能够响应属性值的变化。为此,自定义元素需要将以下成员添加到实现自定义元素的类中:

一个名为 observedAttributes 的静态属性。这必须是一个包含元素需要变更通知的所有属性名称的数组。

attributeChangedCallback() 生命周期回调的实现。

attributeChangedCallback() 回调在列在元素的 observedAttributes 属性中的属性被添加、修改、移除或替换时调用。

回调接受三个参数:

发生变化的属性的名称。

属性的旧值。

属性的新值。

例如,下面这个独立自定义元素将观察一个 size 属性,并在它们发生变化时记录旧值和新值:

js// 为这个元素创建类

class MyCustomElement extends HTMLElement {

static observedAttributes = ["size"];

constructor() {

super();

}

attributeChangedCallback(name, oldValue, newValue) {

console.log(`属性 ${name} 已由 ${oldValue} 变更为 ${newValue}。`);

}

}

customElements.define("my-custom-element", MyCustomElement);

请注意,如果元素的 HTML 声明包含一个被观察的属性,那么在属性被初始化后,attributeChangedCallback() 将在元素的声明首次解析时被调用。因此,在以下示例中,即使属性再也没有被更改,当 DOM 被解析时,attributeChangedCallback() 也会被调用:

html

有关使用 attributeChangedCallback() 的完整示例,请参阅本页面中的生命周期回调。

示例

在本指南的其余部分,我们将看一些示例自定义元素。你可以在 web-components-examples 仓库中找到所有这些示例的源代码,以及更多示例,并且你可以在 https://mdn.github.io/web-components-examples/ 上实时查看它们。

一个独立自定义元素

首先,我们来看一个独立自定义元素。 自定义元素接受图像图标和文本字符串作为属性,并将图标嵌入到页面中。当焦点在图标上时,它会在弹出的信息框中显示文本,以提供更多上下文信息。

查看在线示例

查看源代码

首先,JavaScript 文件定义了一个名为 PopupInfo 的类,该类扩展了 HTMLElement 类。

js// 为当这个元素创建一个类

class PopupInfo extends HTMLElement {

constructor() {

// 必须首先调用 super 方法

super();

}

connectedCallback() {

// 创建影子根

const shadow = this.attachShadow({ mode: "open" });

// 创建几个 span

const wrapper = document.createElement("span");

wrapper.setAttribute("class", "wrapper");

const icon = document.createElement("span");

icon.setAttribute("class", "icon");

icon.setAttribute("tabindex", 0);

const info = document.createElement("span");

info.setAttribute("class", "info");

// 获取属性内容然后将其放入 info 这个 span 内

const text = this.getAttribute("data-text");

info.textContent = text;

// 插入图标

let imgUrl;

if (this.hasAttribute("img")) {

imgUrl = this.getAttribute("img");

} else {

imgUrl = "img/default.png";

}

const img = document.createElement("img");

img.src = imgUrl;

icon.appendChild(img);

// 创建一些 CSS 应用于影子 DOM

const style = document.createElement("style");

console.log(style.isConnected);

style.textContent = `

.wrapper {

position: relative;

}

.info {

font-size: 0.8rem;

width: 200px;

display: inline-block;

border: 1px solid black;

padding: 10px;

background: white;

border-radius: 10px;

opacity: 0;

transition: 0.6s all;

position: absolute;

bottom: 20px;

left: 10px;

z-index: 3;

}

img {

width: 1.2rem;

}

.icon:hover + .info, .icon:focus + .info {

opacity: 1;

}

`;

// 将创建好的元素附加到影子 DOM 上

shadow.appendChild(style);

console.log(style.isConnected);

shadow.appendChild(wrapper);

wrapper.appendChild(icon);

wrapper.appendChild(info);

}

}

类定义包含类的 constructor() 方法,该方法始终以调用 super() 开始,以便正确建立原型链。

在 connectedCallback() 方法内部,我们定义了元素连接到 DOM 时元素将具有的所有功能。在这种情况下,我们将一个影子根附加到自定义元素,使用一些 DOM 操作来创建元素的影子 DOM 结构——然后将其附加到影子根——最后将一些 CSS 附加到影子根以进行样式设置。我们不在构造函数中执行这项工作,因为在连接到 DOM 之前,元素的属性是不可用的。

最后,我们使用前面提到的 define() 方法在 CustomElementRegistry 中注册我们的自定义元素——在参数中,我们指定元素名称,然后定义其功能的类名称:

jscustomElements.define("popup-info", PopupInfo);

现在,它已经可以在我们的页面上使用了。在我们的 HTML 中,我们可以像这样使用它:

html

img="img/alt.png"

data-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.">

引用外部样式

在上面的示例中,我们使用