美文网首页
Web Components 初识

Web Components 初识

作者: AAA前端 | 来源:发表于2019-01-31 14:13 被阅读0次

    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外面的样式不会影响到里面的样式,里面也不会影响外面。

    相关文章

      网友评论

          本文标题:Web Components 初识

          本文链接:https://www.haomeiwen.com/subject/ydmksqtx.html