初探 web components

2,649 阅读5分钟

通过使用 Custom Elements 创建内联的 CSS 和 JavaScript 的自定义元素。需要说明的是它不是 React, Vue 或者 Angular 的框架的替代方案,它是一个全新的概念。

创建完全的自定义元素

CustomElementRegistry 对象

在 window 全局对象下暴露了 customElements 属性,可以通过此属性访问到 CustomElementRegistry 对象。 CustomElementRegistry 对象下的几种方法用于注册 Custom Elements和查询 Custom Elements:

  • define()用于定义新的 Custom Element
  • get() 用于获取 Custom Element 的 constructor,如果不存在,返回 undefined
  • upgrade() 用于升级 Custom Element
  • whenDefined() 用于获取 Custom Element 的 constructor,类似 get(),不同之处是返回值是 promise,有可用值是返回 resolves 状态

如何创建一个 Custom Element

在调用 window.customElements.define() 方法前,首先要定义一个新的HTML元素

class CustomTitle extends HTMLElement {
    // ToDo...
}

在 CustomTitle 类的构造器中使用 Shadow DOM 关联自定义 CSS,JavaScript 和 HTML到新的元素,这样就可以在这个新元素中封装各种功能,首先初始化构造器

class CustomTitle extends HTMLElement {
    constructor() {
        super()
        //...
    }
}

然后调用 attachShadow(),传入参数 { mode: 'open' } ,这个属性设置了 shadow DOM的封装模式,如果设置值为 open,可以访问元素的 shadowRoot 属性,如果值为 close,则不可以。

class CustomTitle extends HTMLElement {
    constructor() {
        super()
        this.attachShadow({ mode: 'open' })
        //...
    }
}

接下来设置元素的HTML

class CustomTitle extends HTMLElement {
    constructor() {
        super()
        this.attachShadow({ mode: 'open' })
        this.shadowRoot.innerHTML = `
            <h1>Hello WC!</h1>
        `
    }
}

这里的 innerHTML 可以写入多个 html tag

定义了内置元素后,即可使用 window.customElements

window.customElements.define('custom-title', CustomTitle)

以上即可在页面中使用

加入CSS

class CustomTitle extends HTMLElement {
  constructor() {
    super()
    this.attachShadow({ mode: 'open' })
    this.shadowRoot.innerHTML = `
      <style>
        h1 {
          font-size: 40px;
          color: #000;
        }
      </style>
      <h1>Hello WC!</h1>
    `
  }
}

简写语法

window.customElements.define('custom-title', class extends HTMLElement {
  constructor() {
    ...
  }
})

加入 JavaScript

在加入JavaScript处理方式上与 CSS 上略有区别,不能再模板字符串中直接写入,需要加入 addEventListener处理

class CustomTitle extends HTMLElement {
    constructor() {
        super()
        this.attachShadow({ mode: 'open' })
        this.shadowRoot.innerHTML = `
        <style>
            h1 {
              font-size: 40px;
              color: #000;
            }
        </style>
        <h1>Hello WC!</h1>
        `
    this.addEventListener('click', e => {
    	console.log('clicked')
    })
  }
}

demo 链接

使用 template

可以使用 template,通过id指定到

<template id="custom-title-template">
  <style>
    h1 {
        font-size: 40px;
        color: #000;
    }
  </style>
  <h1>Hello WC!</h1>
</template>

<custom-title></custom-title>

然后在 Custom Element 构造器中引用到 shadow DOM 上

class CustomTitle extends HTMLElement {
    constructor() {
        super()
        this.attachShadow({ mode: 'open' })
        const tmpl = ducument.querySelector('#custom-title-template')
        this.shadowRoot.appendChild(tmpl.content.cloneNode(true))
  }
}

window.customElements.define('custom-title', CustomTitle)

demo 链接

生命周期

除 constructor 外,还可以定义生命周期函数,用于在特定时期执行特定的方法

  • connectedCallback 当元素插入到DOM中时执行
  • disconnectedCallback 当DOM中删除元素时执行
  • attributeChangedCallback 当检测到属性发生更改或添加或删除时执行(传入3个参数)
    • attrName: 变化了的属性名
    • oldVal: 属性改变前的值
    • newVal: 属性改变后的值
  • adoptedCallback 当元素已移至新文档时
class CustomTitle extends HTMLElement {
    constructor() {
      //...
    }
  
    connectedCallback() {
      //...
    }
  
    disconnectedCallback() {
      //...
    }
    
    attributeChangedCallback(attrName, oldVal, newVal) {
      //...
    }
}

上面提到attributeChangedCallback 会监听属性值的变化,这里要定义监听属性值的范围,必须定义一个静态方法用于返回监听属性的数组

class CustomTitle extends HTMLELement {
    constructor() {
      //...
    }
    
    static get observedAttributes() {
      return ['diabled']
    }
    
    attributeChangedCallback() {
      //...
    }
}

这里我定义了 disabled 属性用于被监听,当它的值变化了,比如它的值为 true

document.querySelector('custom-title').disabled = true

此时,attributeChangedCallback 被触发,三个参数值分别为 'disabled', false, true

定义自定义属性

在自定义元素上定义自定义属性,通过添加 getter 和 setter 函数向自定义元素上定义自定义属性

class CustomTitle extends HTMLElement {
    static get observedAttributes() {
        return ['nbattribute']
    }
    
    get nbattribute() {
        return this.getAttribute('nbattribute')
    }
    
    set nbattribute(value) {
        this.setAttribute('nbattribute', value)
    }
}

如果是定义类似 disabled 这样的布尔属性值,如果值为 true 时展示这个属性值,false 时不展示

class CustomTitle extends HTMLElement {
    static get observedAttributes() {
        return ['boolattribute']
    }
    
    get boolattribute() {
        return this.hasAttribute('boolattribute')
    }
    
    set boolattribute(value) {
        if (value) {
            this.setAttribute('boolattribute', '')
        } else {
            this.removeAttribute('boolattribute')
        }
    }
}

设置尚未定义的自定义元素的样式

在页面加载过程中,JavaScript的加载需要时间,此时自定义元素还未定义,当页面加载完成后,页面的的重新布局过程可能不是那么友好,为了解决这个问题,加入 :not(:defined) 伪类,设置一个大致的高度和渐变效果

custom-title:not(:defined) {
    display: block;
    height: 400px;
    opacity: 0;
    transition: opacity 0.5s ease-in-out;
}

浏览器支持情况 caniuse

最新版的 FireFox,Safari,Chrome,使用 Chromium内核 重写的Edge,IE(就别想了),另外可以加入 polyfill 兼容老版本的浏览器

创建定制化功能的内置元素

所谓定制化功能的内质元素是指对 HTML 中已存在的元素进行定制化功能,对已存在的元素进行功能扩展。在原有的元素上挂上构造器,生命周期钩子等功能特性。 创建定制的内置元素与完全定制化的元素不同,区别在以下列子,接下来要对 Button 做功能扩展,首先需要定义一个类,继承的不是 HTMLElement 而是 HTMLButtonElement

class PlasticButton extends HTMLButtonElement {
     constructor() {
        super()
    
        this.addEventListener('click', e => {
    	  // Do something 
        })
    }
}

// 在定义自定义元素时,需要加入 extends 参数
customElement.define('plastic-button', PlasticButton, { extends: 'button' })

在使用也有不同之处,在原有元素上使用 is 属性指定扩展的元素

<button is="plastic-button"></button>

浏览器支持情况 caniuse

最新版的 FireFox,Safari,Chrome,使用 Chromium内核 重写的Edge,IE(就别想了),另外可以加入 polyfill 兼容老版本的浏览器

Web Components 相关的库

  • Hybrids is a UI library for creating Web Components with simple and functional API.
  • LitElement uses lit-html to render into the element's Shadow DOM and adds API to help manage element properties and attributes. Polymer provides a set of features for creating custom elements.
  • Slim.js is an opensource lightweight web component library that provides data-binding and extended capabilities for components, using es6 native class inheritance.
  • Stencil is an opensource compiler that generates standards-compliant web components.

参考