带你走进->影子元素(Shadow DOM)&浏览器原生组件开发(Web Components API )

本篇介绍

    习惯了应用vuereact等框架来开发组件, 但其实咱们能够不依赖任何框架, 间接原生开发组件, 所以这个原生api的一大长处就是能够不依赖任何的框架。

    浏览器自身反对组件是大趋势, 然而目前应用起来并不够好, 但这并不能阻挡咱们学习的脚步与对常识的好奇心, 而且我也置信原生组件几年后会成为一种支流的组件编写形式, 当初就让咱们一起来学习它吧。

1. 兼容性

Chrome 54 Safari 10.1 Firefox 63

MDN上显示:

    不倡议间接上生产环境。

2. 影子元素

     还记得是我第一次用qiankun.js框架的时候看到的这个概念(接下来的文章会写微前端相干实战), 这个技术能够实现一部分的css款式隔离, 之所以说只是实现一部分款式隔离, 学完这篇文章你就懂了。

第一步: 生成影子元素

    咱们新建一个html5页面, 写上如下构造

<!DOCTYPE html><html lang="en"><head>  <style>    #cc-shadow {      margin: auto;      border: 1px solid #ccc;      width: 200px;      height: 200px;    }  </style></head><body>  <div id="cc-shadow">     <span>我是外部元素</span>  </div>  <script>   const oShadow = document.getElementById("cc-shadow");   const shadow = oShadow.attachShadow({mode: 'open'});   </script></body></html>

    奇怪的一幕呈现了, 外部元素不可见并且在查看构造的控制台里呈现了非凡的构造定义。

  1. attachShadow办法给指定的元素挂载一个Shadow DOM
  2. mode: open 示意能够通过页面内的 JavaScript 办法来获取 Shadow DOM。
  3. mode: open针对是dom.shadowRoot办法, 间接getElementsByClassName获取还是能够获取到的(这条很重要, 有的文章都说错了)。
  4. mode: open对应的是mode: close
  5. 留神: 不能够先开后关这种操作
第二步: 往里面注入元素
 const link = document.createElement("a");    link.href = 'xxxxxxxxxxxx';    link.innerHTML =  '点我跳转';    shadow`.appendChild(link);
  1. 留神这里应用的是shadow, 而不是dom自身。
第三步: 往里面注入款式
 const styles = document.createElement("style"); styles.textContent = `* { color:red  } ` shadow.appendChild(styles);
  1. 通过下面能够看出, 创立了一个style标签插入了进去。
  2. 与此相似咱们能够创立一个link标签插入进来成果也是一样的。

成果如下:

第四步: 款式隔离试验
 styles.textContent = `       * { color:red  }        body {         background-color: red;       }    `

    这里咱们在影子元素外部扭转了body的款式, 而这个款式没有作用到里面的body身上。

第五步: 款式浸透试验

    通过下面操作你是不是感觉这个沙盒能完满隔离css了? 那咱们当初对最外层的style标签外面加上字体大小的款式, 因为影子元素无奈隔离可继承的款式。

* {    font-size: 40px;  }

成果如下:

总结一下:

     影子元素的确能够避免款式泄露到里面净化全局, 然而也没法防止被全局的款式浸透净化, 这里的浸透指的是可继承的款式, 比方你里面style用id获取影子外面的元素扭转border属性那就是有效的, 所以qiankun.js临时无奈完满隔离款式, 比方想要扭转全局款式就须要靠js帮忙。
     有了上述的常识储备, 就让咱们来迎接下一位配角原生组件

残缺代码(来复制玩玩吧):

<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <title>Document</title>  <style>    #cc-shadow {      margin: auto;      border: 1px solid #ccc;      width: 200px;      height: 200px;    }    * {      font-size: 40px;    }  </style></head><body>  <div id="cc-shadow">    <span>我是外部元素</span>  </div>  <script>    // 1: 生成影子元素    const oShadow = document.getElementById("cc-shadow");    const shadow = oShadow.attachShadow({ mode: 'open' });    // 2: 注入元素    const link = document.createElement("a");    link.href = 'xxxxxxxxxxxx';    link.innerHTML = '点我跳转';    shadow.appendChild(link);    // 3: 输出款式    const styles = document.createElement("style");    styles.textContent = `       * { color:red  }        body {         background-color: red;       }    `    // 4: 插入应用,  能够应用插入link的形式插入css, 成果雷同    shadow.appendChild(styles);  </script></body></html>

3. 原生组件的应用

    下图是我做的一个原生组件, 并且附上了应用办法。

 <cc-mw name="大魔王1" image="../imgs/利姆露.webp"/></cc-mw> <cc-mw name="大魔王2" image="../imgs/利姆露.webp"></cc-mw>

下面组件的应用看起来与vue等框架外面的组件差不多, 然而它可是很娇气的!

注意事项
  1. 自定义元素的名称必须蕴含连词线,用与区别原生的 HTML 元素。所以,<cc-mw>不能写成<ccMw>
  2. 如果如下形式书写去掉结尾闭合标签, 只会显示第一个, 第二个没有被渲染(这个真的好奇怪), 第二个组件会默认被插到第一个组件中, 因为被插入影子元素所以不显示了。

    <cc-mw name="大魔王1" image="../imgs/利姆露.webp"/><cc-mw name="大魔王2" image="../imgs/利姆露.webp"/>

    奇奇怪怪的景象不是这次的主题, 咱们持续钻研干货。

4. 编写组件第一步template

template外面的dom构造就相当于影子元素的构造外部。

  <template id="ccmw">    <style>      :host {        border: 1px solid red;        width: 200px;        margin-bottom: 10px;        display: block;        overflow: hidden;      }      .image {        width: 70px;        height: 70px;      }      .container {        border: 1px solid blue;      }      .container>.name {        font-size: 20px;        margin-bottom: 5px;      }    </style>    <img class="image">    <div class="container">      <p class="name"></p>    </div>  </template>

知识点逐个解释:

第一个: dom定义

    下面代码咱们拉倒最上面, 在这里咱们能够失常的定义dom, 释怀书写吧与里面写法一样。

第二个: 定义id

     <template id="ccmw">这句是让写咱们能够找到这个模板。

第三个: <style>标签

    咱们能够当成template标签外部就是一个影子元素的构造外部, 所以这里能够插入款式标签, 并不必js帮助。

第四个: :host

    抉择蕴含应用这段 CSS 的Shadow DOM的影子宿主, 也就是组件的外壳父元素。

5. 组件类

    编写一个组件当然须要逻辑代码啦, 该js闪亮出场了。

  <script>    class CcMw extends HTMLElement {      constructor() {        super();        var shadow = this.attachShadow({ mode: 'closed' });        var templateElem = document.getElementById('ccmw');        var content = templateElem.content.cloneNode(true);        content.querySelector('img').setAttribute('src', this.getAttribute('image'));        content.querySelector('.container>.name').innerText = this.getAttribute('name');        shadow.appendChild(content);      }    }    window.customElements.define('cc-mw', CcMw);  </script>

知识点逐个解释:

第一个: HTMLElement

截取w3school下面的定义, 由此可知这个父类赋予了组件dom元素的根底属性。

第二个: 老朋友attachShadow

    把dom变成影子容器, 这样组件就能够独立进去了。

第三个: templateElem.content.cloneNode(true)

    克隆出模板里的元素, 之所以是克隆因为组件会被复用。

第四个: window.customElements.define('cc-mw', CcMw);

     组件名类名互相绑定, 官网的话就是该对象可用于注册新的自定义元素并获取无关以前注册的自定义元素的信息

第五个: 组件外部获取内部元素

    组件内是能够获取大内部元素的, 所以能够对全局进行操作, 要慎用哦。

    咱们甚至能够间接把组件插入到 body中, 请留神容许, 但不提倡。

第六个: this是谁

    this就是元素自身啦。

学完影子元素是不是就很轻松了解下面的操作都是在干嘛了, 开不开心。

附上残缺代码大家一起玩玩:

<!DOCTYPE html><html><head>  <meta charset="utf-8">  <meta name="viewport" content="width=device-width">  <title>原生组件</title>  <style>    /* 不会影响外部的款式 */    .name {      border: 2px solid red;    }  </style></head><body>  <cc-mw name="大魔王1" image="../imgs/利姆露.webp"/></cc-mw>  <cc-mw name="大魔王2" image="../imgs/利姆露.webp"></cc-mw>    <template id="ccmw">    <style>      :host {        display: block;        overflow: hidden;        border: 1px solid red;        width: 200px;        margin-bottom: 10px;      }      .image {        width: 70px;        height: 70px;      }      .container {        border: 1px solid blue;      }      .container>.name {        font-size: 20px;        margin-bottom: 5px;      }    </style>    <img class="image">    <div class="container">      <p class="name"></p>    </div>  </template>  <script>    class CcMw extends HTMLElement {      constructor() {        super();        var shadow = this.attachShadow({ mode: 'closed' });        var templateElem = document.getElementById('ccmw');        var content = templateElem.content.cloneNode(true);        content.querySelector('img').setAttribute('src', this.getAttribute('image'));        content.querySelector('.container>.name').innerText = this.getAttribute('name');        shadow.appendChild(content);      }    }    window.customElements.define('cc-mw', CcMw);  </script></body></html>

6. 集成为一个js文件

     下面的代码有个问题, 就是怎么组件代码与业务代码放在了一起, 当然咱们能够通过技巧把他们拆散, 这里应用的是模板字符串js生成模板动静插入。

<!DOCTYPE html><html><head>  <meta charset="utf-8">  <meta name="viewport" content="width=device-width">  <title>原生组件</title>  <style>    /* 不会影响外部的款式 */    .name {      border: 2px solid red;    }  </style></head><body>  <cc-mw name="大魔王1" image="../imgs/利姆露.webp"/></cc-mw>  <cc-mw name="大魔王2" image="../imgs/利姆露.webp"></cc-mw>    <script src="./2.拆散.js"></script></body></html>

下面代码清新了很多, 上面咱们能够分心写一个插件了:
./2.拆散.js

const template = document.createElement('template'); template.innerHTML = `  <style>      :host {        border: 2px solid red;        width: 200px;        margin-bottom: 10px;        display: block;        overflow: hidden;      }      .image {        width: 70px;        height: 70px;      }      .container {        border: 1px solid blue;      }      .container>.name {        font-size: 20px;        margin-bottom: 5px;      }    </style>    <img class="image">    <div class="container">      <p class="name"></p>    </div>`class CcMw extends HTMLElement {  constructor() {    super();    var shadow = this.attachShadow({ mode: 'closed' });    var content = template.content.cloneNode(true);    content.querySelector('img').setAttribute('src', this.getAttribute('image'));    content.querySelector('.container>.name').innerText = this.getAttribute('name');    shadow.appendChild(content);  }}window.customElements.define('cc-mw', CcMw);

7. 动静批改数据

     不能批改数据怎么能叫组件那, 这里咱们要利用类的办法。

组件类增加办法:
 class UserCard extends HTMLElement {  constructor() {    // ...    this.oName = content.querySelector('.container>.name');    // ...    shadow.appendChild(content);  }  // 增加办法动静扭转name  changeName(name){    this.oName.innerText = name  }}

咱们在应用组件的页面应用如下代码:(留神: 这里为第一个组件加了id)

 <cc-mw name="大魔王1" id="mw" image="../imgs/利姆露.webp"/></cc-mw> <cc-mw name="大魔王2" image="../imgs/利姆露.webp"></cc-mw>  <script src="./2.拆散.js"></script>  <script>     const mw = document.getElementById('mw');     setTimeout(()=>{       mw.changeName('批改后的魔王');     }, 1000)  </script>

其余的批改办法其实就一模一样了。

8. slot插槽

在模板代码外面加上: (如果不传就显示默认文案)

 <div class="container">      <p class="name"></p>      <slot name="msg">默认文案</slot>    </div>

应用的时候:

<cc-mw name="大魔王1" id="mw" image="../imgs/利姆露.webp"/>     <span slot="msg">进化了</span></cc-mw><cc-mw name="大魔王2" image="../imgs/利姆露.webp"></cc-mw>

成果如下:

end.

     这门技术可能临时没必要太深钻研, 然而学会这门常识能够使咱们有更广大的技术视线, 一直学习总是会有用的, 这次就是这样, 心愿和你一起提高。