Web Component组件
前言:Web Component不是新东西,是几年前的技术,vue等前端框架就是借鉴的Web Component,但是受限于浏览器兼容性,一直没有大规模应用在项目里,直到现在(2018年年末),除IE仍不支持之外,其它主流浏览器都支持Web Component。
一、什么是组件
一个组件就是对局部视图(html、css、js)的一个封装
Web Component不是一个东西,它分为四部分,分别是:
template——模板:
script模板是通过获取script的innerHTML来获取,template则是获取读取template节点的content属性来获取
// 获取模板内容
console.log(document.querySelector('template').content);
HTML import——HTML导入
Shadow DOM——影子DOM:
Web Component 允许内部代码隐藏起来,这叫做 Shadow DOM,即这部分 DOM 默认与外部 DOM 隔离,内部任何代码都无法影响外部。
Vue通过虚拟Dom 和双向绑定实现渲染, Polymer通过shadowDom和双向绑定实现渲染
Custom Elements——自定义元素
二、组件的定义方式
- 定义HTML模板
- 定义CSS样式
- 定义javaScript行为
三、组件的使用方式
大致结构:
- 在body里使用组件,如 < user-card> < /user-ard >
- 在body里创建template模板,主要包含CSS样式
- 在script里创建组件类Class
- 在constructor里创建:继承super()和影子DOM节点
- 在constructor里获取组件的模板,拷贝组件模板中的内容
- 在constructor里设置template模板中的属性,用的是自定义组件中的自定义的属性
- 在constructor里的影子DOM节点中插入组件模板
- 使用浏览器原生的
window.customElements.define()
方法,告诉浏览器<user-card>
元素与这个类关联。
/*
*阮大神的代码借鉴
*/
<body>
<!--使用组件-->
<user-card image="https://semantic-ui.com/images/avatar2/large/kristy.png"
name="User Name"
email="yourmail@some-email.com"></user-card>
<br>
<!--组件的复用-->
<user-card image="https://semantic-ui.com/images/avatar2/large/kristy.png"
name="User Name"
email="yourmail@some-email.com"></user-card>
<!--定义组件的HTML模板-->
<template id="userCardTemplate">
<!--以下HTML结构是为了修改CSS样式以及DOM操作而存在-->
<img class="image">
<div class="container">
<p class="name"></p>
<p class="email"></p>
<button class="button">Follow John</button>
</div>
<!--定义组件的CSS样式,写在template内部,只对template内部的标签有效-->
<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>
</template>
</body>
<script>
class UserCard extends HTMLElement {
constructor() {
super();
//创建影子DOM节点,this指向UserCard
//this.attachShadow()方法的参数{ mode: 'closed'},表示 Shadow DOM 是封闭的,不允许外部访问。
var shadow = this.attachShadow({ mode: 'closed' });//此时shadow相当于影化后的UserCard,可以这么理解
//获取组件模板
var templateElem = document.getElementById('userCardTemplate');
//拷贝组件模板中的内容,主要是一些css样式
var content = templateElem.content.cloneNode(true);
//对template模板进行DOM操作,this指向UserCard
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);//此时shadow相当于影化后的UserCard,也就是说appendChild后,就可以用到content来修改UserCard的样式
}
}
//使用浏览器原生的customElements.define()方法,告诉浏览器<user-card>元素与这个类关联。
window.customElements.define('user-card', UserCard);
</script>
四、如何给组件传值
<body>
<!--相当于自定义一个属性message-->
<hello-world message="好好学习"></hello-world>
<br>
<hello-world message="天天向上"></hello-world>
<template id="hw-template">
<p>Hello World</p>
<style>
p{
padding:10px;
background-color:#f40;
color:#fff
}
</style>
</template>
</body>
<script>
class HelloWorld extends HTMLElement{
constuctor(){
super();
const templateContent = document.querySelector('#hw-template').content
const shadowRoot = this.attachShadow({mode:'open'})
shadowRoot.appendChild(templateContent.cloneNode(true))
//获取属性
const message = this.getAttribute('message')
shadowRoot.querySelector('p').innerText = message
}
}
customElement.define('hello-world')
</script>
五、生命周期
<body>
<hello-world message="好好学习"></hello-world>
<br>
<hello-world message="天天向上"></hello-world>
<template id="hw-template">
<p>Hello World</p>
<style>
p{
padding:10px;
background-color:#f40;
color:#fff
}
</style>
</template>
</body>
<script>
class HelloWorld extends HTMLElement{
constuctor(){
super();
const templateContent = document.querySelector('#hw-template').content
const shadowRoot = this.attachShadow({mode:'open'})
shadowRoot.appendChild(templateContent.cloneNode(true))
const message = this.getAttribute('message')
shadowRoot.querySelector('p').innerText = message
}
//首次插入DOM文档时调用
connectedCallback(){
//在这里发送数据请求(Ajax)
}
//被从文档DOM中删除时调用
disconnectedCallback(){
}
//被移动到新的文档时调用
adoptedCallback(){
}
//当增加、删除、修改自身的属性时被调用
attributeChangedCallback(){
}
}
customElement.define('hello-world')
</script>
六、组件插槽
<body>
<hello-world>
<p slot="header">我是通过插槽进入的组件头部</p>
<p slot="content">我是通过插槽进入的组件内容</p>
</hello-world>
<template id="hw-template">
<div>
<div class="card">
<div class="card-header">
<!--组件插槽-->
<slot name="content"></slot>
</div>
<div class="card-content">
<!--组件插槽-->
<slot name="content"></slot>
</div>
</div>
</div>
<style>
.card{
width:500px;
height:300px;
border:1px soild #000;
padding:5px;
}
.card-header{
padding:10px;
background-color:#ccc
}
</style>
</template>
</body>
<script>
class HelloWorld extends HTMLElement{
constuctor(){
super();
const templateContent = document.querySelector('#hw-template').content
const shadowRoot = this.attachShadow({mode:'open'})
shadowRoot.appendChild(templateContent.cloneNode(true))
}
connectedCallback(){
}
disconnectedCallback(){
}
adoptedCallback(){
}
attributeChangedCallback(){
}
}
customElement.define('hello-world')
</script>