首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript高级程序设计》笔记
    • TypeScript
    • 小程序笔记
    • JS设计模式总结
  • 网络

    • 网络初探
    • http状态码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 学习
  • 面试
  • 心情杂货
  • 实战例子
  • 实用技巧
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Arthas

划水工程师
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript高级程序设计》笔记
    • TypeScript
    • 小程序笔记
    • JS设计模式总结
  • 网络

    • 网络初探
    • http状态码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 学习
  • 面试
  • 心情杂货
  • 实战例子
  • 实用技巧
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 基础
  • 内置对象
  • 面向对象
  • 异步操作
  • DOM
  • 事件
  • 浏览器模型
  • 自行实现bind函数
  • web component
    • 组件定义
    • template使用
    • 添加样式
    • 自定义元素的参数
    • Shadow DOM
    • 扩展
  • 《JavaScript教程》笔记
Arthas
2023-03-27
目录

web component

目前主流框架的组件编写方式都是基于web components API实现的,所以想要了解框架,就也需要去了解web components 定义和使用。

# 组件定义

class UserCard extends HTMLElement {
  constructor() {
    super();
  }
}

window.customElements.define('user-card', UserCard);
1
2
3
4
5
6
7

目前已经完成一个空的自定义元素了,我们需要往里面添加组件内容

class UserCard extends HTMLElement {
  constructor() {
    super();

    var image = document.createElement('img');
    image.src = 'https://semantic-ui.com/images/avatar2/large/kristy.png';
    image.classList.add('image');

    var container = document.createElement('div');
    container.classList.add('container');

    var name = document.createElement('p');
    name.classList.add('name');
    name.innerText = 'User Name';

    var email = document.createElement('p');
    email.classList.add('email');
    email.innerText = 'yourmail@some-email.com';

    var button = document.createElement('button');
    button.classList.add('button');
    button.innerText = 'Follow';

    container.append(name, email, button);
    this.append(image, container);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

可以看出来这个组件有img,div,p,button元素,完成这一步以后,自定义元素内部的DOM结构就已经生成。

# template使用

用js写DOM结构很麻烦,web component API提供了template标签,在template里面自定义DOM。

<template id="userCardTemplate">
  <img src="https://semantic-ui.com/images/avatar2/large/kristy.png" class="image">
  <div class="container">
    <p class="name">User Name</p>
    <p class="email">yourmail@some-email.com</p>
    <button class="button">Follow</button>
  </div>
</template>
1
2
3
4
5
6
7
8

再改写通过id加载template

class UserCard extends HTMLElement {
  constructor() {
    super();

    var templateElem = document.getElementById('userCardTemplate');
    var content = templateElem.content.cloneNode(true);
    this.appendChild(content);
  }
}  
1
2
3
4
5
6
7
8
9

# 添加样式

自定义组件没有样式,需要指定全局样式

user-card {
  /* ... */
}
1
2
3

但如果想样式只是在template中生效,可以在template中定义样式

<template id="userCardTemplate">
  <style>
    :host {
      display: flex;
      align-items: center;
      width: 450px;
      height: 180px;
      background-color: #d4d4d4;
      border: 1px solid #d5d5d5;
      box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
      border-radius: 3px;
      overflow: hidden;
      padding: 10px;
      box-sizing: border-box;
      font-family: 'Poppins', sans-serif;
    }
    .image {
      flex: 0 0 auto;
      width: 160px;
      height: 160px;
      vertical-align: middle;
      border-radius: 5px;
    }
    .container {
      box-sizing: border-box;
      padding: 20px;
      height: 160px;
    }
    .container > .name {
      font-size: 20px;
      font-weight: 600;
      line-height: 1;
      margin: 0;
      margin-bottom: 5px;
    }
    .container > .email {
      font-size: 12px;
      opacity: 0.75;
      line-height: 1;
      margin: 0;
      margin-bottom: 15px;
    }
    .container > .button {
      padding: 10px 25px;
      font-size: 12px;
      border-radius: 5px;
      text-transform: uppercase;
    }
  </style>

  <img src="https://semantic-ui.com/images/avatar2/large/kristy.png" class="image">
  <div class="container">
    <p class="name">User Name</p>
    <p class="email">yourmail@some-email.com</p>
    <button class="button">Follow</button>
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

# 自定义元素的参数

<user-card
  image="https://semantic-ui.com/images/avatar2/large/kristy.png"
  name="User Name"
  email="yourmail@some-email.com"
></user-card>
1
2
3
4
5

template和类的内容就需要修改一下

<template id="userCardTemplate">
  <style>...</style>
  <img class="image">
  <div class="container">
    <p class="name"></p>
    <p class="email"></p>
    <button class="button">Follow John</button>
  </div>
</template>
1
2
3
4
5
6
7
8
9
class UserCard extends HTMLElement {
  constructor() {
    super();
    var templateElem = document.getElementById('userCardTemplate');
    var content = templateElem.content.cloneNode(true);
    content.querySelector('img').setAttribute('src', this.getAttribute('image'));
    content.querySelector('.container>.name').innerText = this.getAttribute('name');
    content.querySelector('.container>.email').innerText = this.getAttribute('email');
    this.appendChild(content);
  }
}
window.customElements.define('user-card', UserCard);   
1
2
3
4
5
6
7
8
9
10
11
12

# Shadow DOM

如果我们不需要显示出组件内部内容,就使用this.attachShawdow( { mode: 'closed' } )关闭shadow DOM 最后完整代码显示

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
<user-card
  image="https://semantic-ui.com/images/avatar2/large/kristy.png"
  name="User Name"
  email="yourmail@some-email.com"
>
</user-card>
  
<template id="userCardTemplate">
  <style>
   :host {
     display: flex;
     align-items: center;
     width: 450px;
     height: 180px;
     background-color: #d4d4d4;
     border: 1px solid #d5d5d5;
     box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
     border-radius: 3px;
     overflow: hidden;
     padding: 10px;
     box-sizing: border-box;
     font-family: 'Poppins', sans-serif;
   }
   .image {
     flex: 0 0 auto;
     width: 160px;
     height: 160px;
     vertical-align: middle;
     border-radius: 5px;
   }
   .container {
     box-sizing: border-box;
     padding: 20px;
     height: 160px;
   }
   .container > .name {
     font-size: 20px;
     font-weight: 600;
     line-height: 1;
     margin: 0;
     margin-bottom: 5px;
   }
   .container > .email {
     font-size: 12px;
     opacity: 0.75;
     line-height: 1;
     margin: 0;
     margin-bottom: 15px;
   }
   .container > .button {
     padding: 10px 25px;
     font-size: 12px;
     border-radius: 5px;
     text-transform: uppercase;
   }
  </style>
  
  <img class="image">
  <div class="container">
    <p class="name"></p>
    <p class="email"></p>
    <button class="button">Follow John</button>
  </div>
</template>

</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
class UserCard extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow( { mode: 'closed' } );
    const templateElem = document.getElementById('userCardTemplate');
    const content = templateElem.content.cloneNode(true);
    content.querySelector('img').setAttribute('src', this.getAttribute('image'));
    content.querySelector('.container>.name').innerText = this.getAttribute('name');
    content.querySelector('.container>.email').innerText = this.getAttribute('email');

    shadow.appendChild(content);
  }
}
window.customElements.define('user-card', UserCard);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 扩展

添加事件

this.$button = shadow.querySelector('button');
this.$button.addEventListener('click', () => {
  // do something
});
1
2
3
4

注:封装好该组件代码,将template注入网页后,可以封装成一个js组件,能在网页上直接编写user-card这个组件。

编辑 (opens new window)
上次更新: 2023/03/27, 17:49:25
自行实现bind函数

← 自行实现bind函数

最近更新
01
webpack、vite性能优化
05-24
02
笔试题记录
05-23
03
如何超过大多数人
05-16
更多文章>
Theme by Vdoing | Copyright © 2023-2023 Arthas | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式