Skip to content

简介

由三项主要技术组成:

  • Custom element(自定义元素):一组 JavaScript API,允许你定义 custom elements 及其行为,然后可以在你的用户界面中按照需要使用它们。
  • Shadow DOM(影子 DOM):一组 JavaScript API,用于将封装的“影子”DOM 树附加到元素(与主文档 DOM 分开呈现)并控制其关联的功能。通过这种方式,你可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
  • HTML template(HTML 模板): <template> 和 <slot> 元素使你可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用

自定义元素

分类

  • 自定义内置元素:自定义内置元素是指基于现有的 HTML 元素创建的新元素。这些元素继承了现有 HTML 元素的行为和属性,并在此基础上添加新的功能

    javascript
    /** html **/
    <button is="custom-button">Click me!</button>
    
    /** js **/
    class CustomButton extends HTMLButtonElement {
      constructor() {
        super();
        this.addEventListener('click', () => {
          alert('Button clicked!');
        });
      }
    }
    
    window.customElements.define('custom-button', CustomButton, { extends: 'button' });
  • 独立自定义元素:独立自定义元素是指完全自定义的新元素,它们不是基于任何现有 HTML 元素创建的。(UI组件)

    javascript
    <my-element></my-element>
    
    class MyElement extends HTMLElement {
      constructor() {
        super();
        const shadowRoot = this.attachShadow({ mode: 'open' });
        shadowRoot.innerHTML = '<p>Hello, I am a custom element!</p>';
      }
    }
    
    window.customElements.define('my-element', MyElement);

自定义元素生命周期

  • connectedCallback():每当元素添加到文档中时调用。规范建议开发人员尽可能在此回调中实现自定义元素的设定,而不是在构造函数中实现。
  • disconnectedCallback():每当元素从文档中移除时调用。
  • adoptedCallback():每当元素被移动到新文档中时调用。
  • attributeChangedCallback():在属性更改、添加、移除或替换时调用。有关此回调的更多详细信息,请参见响应属性变化
html
<my-custom-element size="100">111</my-custom-element>
javascript
class MyCustomElement extends HTMLElement {
    static observedAttributes = ['size']

    constructor() {
      super();

      this.addEventListener('click', (e) => {
        e.stopPropagation();
        this.setAttribute('size', 200)
      }, false)
    }
    
	  connectedCallback() {
	    console.log("自定义元素添加至页面。");
	  }
	
	  disconnectedCallback() {
	    console.log("自定义元素从页面中移除。");
	  }
	
	  adoptedCallback() {
	    console.log("自定义元素移动至新页面。");
	  }

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

  customElements.define('my-custom-element', MyCustomElement)

shadow DOM

dom结构

Untitled.png

  • 影子宿主(Shadow host): 影子 DOM 附加到的常规 DOM 节点。
  • 影子树(Shadow tree): 影子 DOM 内部的 DOM 树。
  • 影子边界(Shadow boundary): 影子 DOM 终止,常规 DOM 开始的地方。
  • 影子根(Shadow root): 影子树的根节点。

示例

javascript
<div id="host"></div>

const host = document.querySelector('#host');

const shadowDom = host.attachShadow({ mode: 'open' });

const span = document.createElement('span');

span.innerHTML = `<span>内部元素</span>`;
span.setAttribute('class', 'innerClass');

shadowDom.appendChild(span);

console.log(host.shadowRoot) // #shadow-root

当 mode 设置为 "open" 时,页面中的 JavaScript 可以通过影子宿主的 shadowRoot 属性访问影子 DOM 的内部。当 mode 设置为 "false" 时,shadowRoot 返回 null

  • 页面的 CSS 不会影响shadowRoot DOM 内的节点:
  • shadowRoot DOM 树中定义的样式局限在该树内,shadowRoot DOM 样式不会影响页面中其它元素的样式

template & slot

template

元素及其内容不会在 DOM 中渲染,但仍可使用 JavaScript 引用它;

javascript
<template id="my-paragraph">
  <p>我的段落</p>
</template>

let template = document.getElementById("my-paragraph");
let templateContent = template.content;
document.body.appendChild(templateContent);

slot

html
<template id="template-fragment">
    <style>
      * {
        text-align: center;
        font-size: 30px;
        font-weight: 600;
        color: yellowgreen;
        margin: 0 auto;
        width: 100%;
      }
      span {
        display: inline-block;
      }
    </style>
    <span>template-fragment</span>
    <div style="width: 100%; height: 50px; background: #ccc;"> 
      <slot>default slot</slot>
    </div>
    <div>
      <slot name="header">header</slot>
    </div>
    <div>
      <slot name="footer">footer</slot>
    </div>
  </template>
  
  <custom-element>
    <span>default-content</span>
    <span slot="header">header-hello</span>

    <span slot="footer">footer-hello</span>
  </custom-element>
javascript
class CustomElement extends HTMLElement {
    constructor() {
      super();

      this.init()
    }

    init() {
      const fragment = document.getElementById('template-fragment');

      const content = fragment.content;

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

      shadow.appendChild(content);
    }
  }

  customElements.define('custom-element', CustomElement)

Untitled.png

Released under the MIT License.