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:

customElements.define('word-count', WordCount, { extends: 'p' });

这个元素叫做 word-count,它的类对象是 WordCount, 继承自

元素.

一个 custom element 的类对象可以通过 ES 2015 标准里的类语法生成。所以,WordCount可以写成下面这样:

class WordCount extends HTMLParagraphElement {
  constructor() {
    // 必须首先调用 super 方法
    super();

    // 元素的功能代码写在这里

    ...
  }
}

上面只是一个简单的例子,我们能做的不只这些。在构造函数中,我们可以设定一些生命周期的回调函数,在特定的时间,这些回调函数将会被调用。例如,connectedCallback会在 custom element首次被插入到文档DOM节点上时被调用,而 attributeChangedCallback则会在 custom element增加、删除或者修改某个属性时被调用。

你可以在 使用生命周期回调函数段落中了解更多相关信息。

共有两种 custom elements:

  • Autonomous custom elements 是独立的元素,它不继承其他内建的HTML元素。你可以直接把它们写成HTML标签的形式,来在页面上使用。例如 ,或者是document.createElement("popup-info")这样。
  • Customized built-in elements 继承自基本的HTML元素。在创建时,你必须指定所需扩展的元素(正如上面例子所示),使用时,需要先写出基本的元素标签,并通过 is 属性指定custom element的名称。例如

    , 或者 document.createElement("p", { is: "word-count" })

示例

让我们来看几个简单示例,来了解如何创建 custom elements。

Autonomous custom elements

我们来看一下  (查看在线示例),一个关于 autonomous custom element的例子。它包含有一个图标和一段文字,并且图标显示在页面上。在这个图标获取焦点时,它会显示一个包含该段文字的信息框,用于展示更多的信息。

为了实现这个功能,首先创建一个JavaScript文件,定义一个叫做PopUpInfo的类,它继承自HTMLElement。Autonomous custom elements 总是继承自HTMLElement

class PopUpInfo extends HTMLElement {
  constructor() {
    // 必须首先调用 super方法 
    super(); 

    // 元素的功能代码写在这里

    ...
  }
}

上述代码片段中,类的构造函数constructor总是先调用super()来建立正确的原型链继承关系。

在构造函数中,我们会定义元素实例所拥有的全部功能。作为示例,我们首先会将shadow root附加到custom element上,然后通过一系列DOM操作创建custom element的内部阴影DOM结构,再将其附加到 shadow root上,最后再将一些CSS附加到 shadow root的style节点上。

// 创建一个 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上,在方法的参数里,我们指定了元素的名称,以及定义了元素功能的类。

customElements.define('popup-info', PopUpInfo);

现在我们可以在页面上使用我们定义的custom element了,就像下面这样:

<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 (查看在线示例)。该示例将所有的无序列表转化为一个可收起/展开的菜单。

首先,我们定义一个元素的类,这和之前一样:

class ExpandingList extends HTMLUListElement {
  constructor() {
    // 必须首先调用 super方法 
    super();

    // 元素的功能代码写在这里

    ...
  }
}

在这里,我们不会详细解释元素的功能细节,你可以在源码中了解它的工作方式。这里的真正不同点在于元素继承的是HTMLUListElement 接口,而不是HTMLElement。所以它拥有

    元素所有的特性,以及在此基础上我们定义的功能,这是与独立元素(standalone element)不同之处。这也是为什么我们称它为 customized built-in元素,而不是一个autonomous元素。

    接下来,和之前一样,我们使用define()方法注册一个元素,但不同的是,我们需要添加一个配置对象,用于指定我们需要继承的元素:

    customElements.define('expanding-list', ExpandingList, { extends: "ul" });

    在页面上使用 built-in element看起来也会有所不同:

    <ul is="expanding-list">
    
      ...
    
    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看起来像下面这样:

      <custom-square l="100" c="red">custom-square>

      这里,类的构造函数很简单 — 我们将 shadow DOM附加到元素上,然后将一个

      元素和