认识WebComponents

404 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

WebComponents简介

众所周知,复杂的程序可以拆解为一个一个小的简单的程序,这样就可以把复杂的架构简单化,我们可以通过开发一个一个的可视化组件,将组件的逻辑写入内部,并将简单组件组合形成一个复杂的项目,从而避免我们的项目变得过于复杂且混乱,而WebCOmponents便可以完成这个任务。WebComponents是一套可以用于创建重复的定制元素,并在web中进行复用的技术,这就Vue、React等框架的组件类似,但WebComponents是可以被原生的浏览器所支持的,并不需要runtime文件。现在也有一些微前端框架使用了

WebComponents技术组成

WebComponents技术进行实现,从而实现,样式隔离,技术栈无关等特点。WebComponent主要由一些三个部分组成:

  • Custom Element: 主要用于定义一个自定义的HTML元素,并封装该自定义元素的行为,这就类似于JSP技术中的自定义标签
  • Shadow DOM: 主要是用于创建一个内部的DOM元素,这个DOM对外界是不可见的,是一个独立的文档上下文,从而可以保证元素的私有化,而不必担心与其他文档的冲突
  • CSS Scoping: 声明仅用于组件的Shadow DOM内的样式
  • Template and slot: 主要用于HTML片段的复用,可以用于编写不在页面中展示的模板,然后作为自定义元素被复用

以上这些技术是实现WebComponents技术的一些原生的WebAPI支持,从而来帮助我们更好的构建组件化的应用

WebComponents实现步骤

  • 创建一个类来定义自定义组件的功能
  • 使用 CustomElementRegistry.define() 来定义一个Custom Element元素,此处可以定义我们定义的组件的自定义元素的名称,以及指定该组件处理的类,我们使用上一步所创建的类
  • 通常情况下,为了使得组件的私有化,我们会使用Element.attachShadow() 方法将一个shadow DOM附加到自定义元素上。然后我们向Shadow DOM内添加子元素,监听事件等,从而可以使得这些操作不会对外界造成影响。
  • 对于一些复杂的元素,我们可以通过用 <template>定义可复用的标记模板,然后我们通常会调用templateElement.context.cloneNode(),将template内的DOM的副本添加到文档内,一些元素也可以通过<slot>对一些元素定义插槽,这个就类似于Vue中的slot
  • 然后我们可以像使用HTML原生元素一样,在HTML中使用我们的自定义元素
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <firstComponents id="first" img="./alt.jpg" text="测试文字"></firstComponents>
</body>
<script>
// 创建一个类
class FirstComponents extends HTMLElement {
  constructor() {
    super();
​
    // 创建一个ShadowDOM
    //此处为挂载shadowDOM的关键
    const shadow = this.attachShadow({mode: 'open'});
    //不可以挂载真实DOM,不符合HTML5标准要求
    //const shadow = document.createElement('div');
    //this.append(shadow)
    //创建标签
    const wrapper = document.createElement('div');
    wrapper.setAttribute('class', 'wrapper');
​
    const icon = document.createElement('span');
    icon.setAttribute('class', 'icon');
    icon.setAttribute('tabindex', 0);
​
    const info = document.createElement('span');
    info.setAttribute('class', 'info');
​
    // Take attribute content and put it inside the info span
    const text = this.getAttribute('text');
    info.textContent = text;
​
    // 插入图标
    let imgUrl;
    if(this.hasAttribute('img')) {
      imgUrl = this.getAttribute('img');
    } else {
      imgUrl = './default.jpg';
    }
​
    const img = document.createElement('img');
    img.src = imgUrl;
    icon.appendChild(img);
​
    // 创建CSS样式,并用于Shadow DOM,在Shodow DOM上的样式不会影响外部文档的样式。
    const style = document.createElement('style');
    style.textContent = `
      .wrapper {
        width: 100%;
        display: flex;
        flex-direction: column;
        align-items: center;
        position: relative;
        margin: 50px;
      }
      .info {
        font-size: 0.8rem;
        width: 200px;
        display: inline-block;
        border: 1px solid black;
        padding: 10px;
        background: white;
        border-radius: 10px;
        opacity: 0;
        transition: 0.6s all;
        position: absolute;
        bottom: 20px;
        left: 10px;
        z-index: 3;
      }
      img {
        width:300px;
        heigjt: 100%
      }
      .icon:hover + .info, .icon:focus + .info {
        opacity: 1;
      }
    `;
​
    // 将节点挂载到Shadow DOM
    shadow.appendChild(style);
    shadow.appendChild(wrapper);
    wrapper.appendChild(icon);
    wrapper.appendChild(info);
  }
}
​
// 定义Custom element
customElements.define('firstComponents', FirstComponents);
</script>
</html>