Web Components 初识

Web components可以将html标签结构、css样式和行为隐藏起来,并从页面上的其他代码中分离开来,这样不同的功能不会混在一起,封装为 custom elements(自定义标签),并且可以运用template重复html结构片段,你用slot配合创建弹性模板

1.custom elements (自主自定义标签----创建一个标签)

基本用法: customElements.define(name, constructor[, options]);

  • name --一个DOMString, 用于表示所创建的元素的名称。注意,custom element 的名称中必须要有短横线
  • constructor 一个类对象,用于定义元素的行为
  • options 可选参数 包含 extends属性的配置对象,指定了所创建的元素继承自哪个内置元素,可以继承任何内置元素

栗子

customElements.define('word-count', WordCount, { extends: 'p' }); // 这个元素叫做 `word-count`,它的类对象是 `WordCount`, 继承自 <p>元素

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

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

    ...
  }
}
共有两种 custom elements:
  1. Autonomous custom elements 是独立的元素,它不继承其他内建的HTML元素。你可以直接把它们写成HTML标签的形式,来在页面上使用。例如 <popup-info>,或者是document.createElement("popup-info")这样
class PopUpInfo extends HTMLElement {
  constructor() {
    // 必须首先调用 super方法 
    super(); 

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

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

// 页面上
<popup-info>
  1. Customized built-in elements 继承自基本的HTML元素。在创建时,你必须指定所需扩展的元素(正如上面例子所示),使用时,需要先写出基本的元素标签,并通过 is 属性指定custom element的名称。例如<p is="word-count">, 或者 document.createElement("p", { is: "word-count" })
class ExpandingList extends HTMLUListElement {   // 这里的真正不同点在于元素继承的是HTMLUListElement接口,而不是HTMLElement

  constructor() {
    // 必须首先调用 super方法 
    super();

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

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


// 页面上
<ul is="expanding-list">

  ...

</ul>

参考: https://developer.mozilla.org/zh-CN/docs/Web/Web_Components/Using_custom_elements

2. shadow DOM (Shadow DOM允许将隐藏的DOM树添加到常规的DOM树中)

image
  • 需要了解的 Shadow DOM相关技术:
  • Shadow host: 一个常规 DOM节点,Shadow DOM会被添加到这个节点上
  • Shadow tree:Shadow DOM内部的DOM树。
  • Shadow boundary:Shadow DOM结束的地方,也是常规 DOM开始的地方。
  • Shadow root: Shadow tree的根节点。

基本用法: element.attachShadow({mode: 'open' | 'closed'});

  • mode 接受open 或者 closed;
  • open 表示你可以通过页面内的 JavaScript 方法来获取 Shadow DOM
  • 如果你将一个 Shadow root 添加到一个 Custom element 上,并且将 mode设置为closed,那么就不可以在外部获取 Shadow DOM了——myCustomElem.shadowRoot 将会返回 null。浏览器中的某些内置元素就是这样的,例如<video>,包含了不可访问的 Shadow DOM

简单的栗子:

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

var wrapper = document.createElement('span');
wrapper.setAttribute('class','wrapper');

shadow.appendChild(wrapper);

复杂点的栗子:(与custom elements配合使用)

// main.js
// Create a class for the element
class Square extends HTMLElement {
  // Specify observed attributes so that
  // attributeChangedCallback will work
// 触发 attributeChangedCallback()回调函数,必须监听这个属性
// 通过定义observedAttributes() get函数来实现,observedAttributes()函数体内包含一个 return语句,返回一个数组,包含了需要监听的属性名称
  static get observedAttributes() {
    return ['c', 'l'];
  }

  constructor() {
    // Always call super first in constructor
    super();

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

    const div = document.createElement('div');
    const style = document.createElement('style');
    shadow.appendChild(style);
    shadow.appendChild(div);
  }
 // 当 custom element首次被插入文档DOM时,被调用。
  connectedCallback() {
    console.log('Custom square element added to page.');
    updateStyle(this);
  }
// 当 custom element从文档DOM中删除时,被调用。
  disconnectedCallback() {
    console.log('Custom square element removed from page.');
  }
// 当 custom element被移动到新的文档时,被调用。
  adoptedCallback() {
    console.log('Custom square element moved to new page.');
  }
// 当 custom element增加、删除、修改自身属性时,被调用。
  attributeChangedCallback(name, oldValue, newValue) {
    console.log('Custom square element attributes changed.');
    updateStyle(this);
  }
}

customElements.define('custom-square', Square);

function updateStyle(elem) {
  const shadow = elem.shadowRoot;
  const childNodes = Array.from(shadow.childNodes);
  
  childNodes.forEach(childNode => {
    if (childNode.nodeName === 'STYLE') {
      childNode.textContent = `
        div {
          width: ${elem.getAttribute('l')}px;
          height: ${elem.getAttribute('l')}px;
          background-color: ${elem.getAttribute('c')};
        }
      `;
    }
  });
}

const add = document.querySelector('.add');
const update = document.querySelector('.update');
const remove = document.querySelector('.remove');
let square;

update.disabled = true;
remove.disabled = true;

function random(min, max) {
  return Math.floor(Math.random() * (max - min + 1) + min);
}

add.onclick = function() {
  // Create a custom square element
  square = document.createElement('custom-square');
  square.setAttribute('l', '100');
  square.setAttribute('c', 'red');
  document.body.appendChild(square);

  update.disabled = false;
  remove.disabled = false;
  add.disabled = true;
};

update.onclick = function() {
  // Randomly update square's attributes
  square.setAttribute('l', random(50, 200));
  square.setAttribute('c', `rgb(${random(0, 255)}, ${random(0, 255)}, ${random(0, 255)})`);
};

remove.onclick = function() {
  // Remove the square
  document.body.removeChild(square);

  update.disabled = true;
  remove.disabled = true;
  add.disabled = false;
};
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Life cycle callbacks test</title>
    <style>
      custom-square {
        margin: 20px;
      }
      div{
          border: 1px solid #ccc !important;
      }
    </style>
    <script defer src="main.js"></script>
  </head>
  <body>
    <h1>Life cycle callbacks test</h1>
    <ul is="custom-square"></ul>
    <div>
      <button class="add">Add custom-square to DOM</button>
      <button class="update">Update attributes</button>
      <button class="remove">Remove custom-square from DOM</button>
    </div>

  </body>
</html>

*由shadow DOM里面添加的样式不会影响到外面,而且外面也不会影响里面。

参考: https://developer.mozilla.org/zh-CN/docs/Web/Web_Components/Using_shadow_DOM

3. template slots

  • template标签里的代码不会展现在页面中,知道js获取它的引用,然后添加到dom中,才会展示。
// html
<template id="my-paragraph">
  <p>My paragraph</p>
</template>

// js
let template = document.getElementById('my-paragraph');
let templateContent = template.content;
document.body.appendChild(templateContent);
  • 和web组件一起使用template
customElements.define('my-paragraph',
  class extends HTMLElement {
    constructor() {
      super();
      let template = document.getElementById('my-paragraph');
      let templateContent = template.content;
      
    //Node.cloneNode() 方法添加了模板(template) 的拷贝到阴影(shadow) 的根结点上.
      const shadowRoot = this.attachShadow({mode: 'open'})
        .appendChild(templateContent.cloneNode(true));  

  }
})
<template id="my-paragraph">
  <style>
    p {
      color: white;
      background-color: #666;
      padding: 5px;
    }
  </style>
  <p>My paragraph</p>
</template>
<my-paragraph></my-paragraph>

slot增加灵活度

  • 我们将模板(tempalte) 的 p 标签改成下面这样
<p><slot name="my-text">My default text</slot></p>
<my-paragraph>
  <span slot="my-text">Let's have some different text!</span>
</my-paragraph>

或者

<my-paragraph>
  <ul slot="my-text">
    <li>Let's have some different text!</li>
    <li>In a list!</li>
  </ul>
</my-paragraph>
  • 这样就可以灵活的添加内容到模板中了。

栗子:

<!-- Learn about this code on MDN: https://developer.mozilla.org/zh-CN/docs/Web/Web_Components/Using_templates_and_slots -->

<!DOCTYPE html>
<html>
  <head>
    <title>slot example</title>
    <style>

      dl { margin-left: 6px; }
      dt { font-weight: bold; color: #217ac0; font-size: 110% }
      dt { font-family: Consolas, "Liberation Mono", Courier }
      dd { margin-left: 16px }

    </style>
  </head>
  <body>
    <template id="element-details-template">
      <style>
      details {font-family: "Open Sans Light",Helvetica,Arial}
      .name {font-weight: bold; color: #217ac0; font-size: 120%}
      h4 { margin: 10px 0 -8px 0; }
      h4 span { background: #217ac0; padding: 2px 6px 2px 6px }
      h4 span { border: 1px solid #cee9f9; border-radius: 4px }
      h4 span { color: white }
      .attributes { margin-left: 22px; font-size: 90% }
      .attributes p { margin-left: 16px; font-style: italic }
      </style>
      <details>
        <summary>
          <span>
            <code class="name">&lt;<slot name="element-name">NEED NAME</slot>&gt;</code>
            <i class="desc"><slot name="description">NEED DESCRIPTION</slot></i>
          </span>
        </summary>
        <div class="attributes">
          <h4><span>Attributes</span></h4>
          <slot name="attributes"><p>None</p></slot>
        </div>
      </details>
      <hr>
    </template>

    <element-details>
      <span slot="element-name">slot</span>
      <span slot="description">A placeholder inside a web
        component that users can fill with their own markup,
        with the effect of composing different DOM trees
        together.</span>
      <dl slot="attributes">
        <dt>name</dt>
        <dd>The name of the slot.</dd>
      </dl>
    </element-details>

    <element-details>
      <span slot="element-name">template</span>
      <span slot="description">A mechanism for holding client-
        side content that is not to be rendered when a page is
        loaded but may subsequently be instantiated during
        runtime using JavaScript.</span>
    </element-details>

    <script>
    customElements.define('element-details',
      class extends HTMLElement {
        constructor() {
          super();
          const template = document
            .getElementById('element-details-template')
            .content;
          const shadowRoot = this.attachShadow({mode: 'open'})
            .appendChild(template.cloneNode(true));
        }
      })
    </script>
  </body>
</html>
  • template外面的样式不会影响到里面的样式,里面也不会影响外面。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容