熟悉Vue开发和React开发的小伙伴们肯定特别熟悉“组件”这个概念。
通过创建组件,我们获得了功能独立、样式独立的HTML元素片段组。
我们知道,在Vue或React中,所谓的自定义组件,最终还是会编译为基本元素的嵌套与复杂的js逻辑。
当然,这些框架中的组件,还提供了模版语法等优秀的开发方式,以及跨组件数据流等优秀的功能。但是,抛开这些不谈,真正的将“自定义元素”搬上页面的,是 Web Components
规范。
PS:原生万岁。
1. 基本实现
首先,我们需要定义一个类,实现自定义元素的样式、内容与功能。
这个类需要继承自 HTMLElement
类,从而成为独立元素,或者继承 HTMLUListElement
等基本HTML元素类。
在类的构造函数中,需要首先调用 super
方法。然后,再进行内部的构建(元素结构、样式、事件监听等)。
class CommentCard extends HTML{
constructor(){
super();
...
}
}
然后,通过以下方法注册custom elements:
customElements.define('comment-card', CommentCard);
define函数的第一个参数为要使用的标签名称,第二个参数为我们创建的类。
如果我们的 custom elements 继承自基本HTML元素类,还要为define函数传递第三个参数:
customElements.define('comment-card', CommentCard, {extends: 'ul'});
使用:
//独立元素
<comment-card></comment-card>
//继承元素
<ul is="comment-card">
...
</ul>
2. 使用<template>标签实现内部DOM结构构件与样式
我们可以通过原生的DOM操作,来构建 custom elements 内部的结构,并设置样式,如下:
class CommentCard extends HTML{
constructor(){
super();
let $info = document.createElement('div');
$info.classList.add('info');
let $avatar = document.createElement('img');
let $nickname = document.createElement('div');
$info.appendChild($avatar);
$info.appendChild($nickname);
//所有的DOM结构都挂在自定义标签内
this.appendChild($info);
}
}
不过这样真的是太麻烦了。
更好的方式是,我们把自定义标签的 内部结构与样式 统统写在HTML文档的template
标签内部,然后在 custom elements 类的构造函数中,使用DOM方法将其内容取出,复制并挂载到自定义元素中。
<template id="commentCardTemplate">
<style>
/* :host伪类代表元素自身的样式 */
:host{
...
}
.info{
...
}
...
</style>
<div class="info">
<div class="avatar">
<img src="" alt="" />
</div>
<div class="other">
<div class="nickname"></div>
<div class="time"></div>
</div>
</div>
<div class="text"></div>
</template>
class CommentCard extends HTMLElement{
constructor(){
super();
let templateEle = document.getElementById('commentCardTemplate');
let content = templateEle.content.cloneNode(true);
this.appendChild(content);
}
}
为了保证代码的可维护性,我们可以把template
元素以及内部内容,和 custom elements 的定义都封装在一个js文件中。
let tmpl = `
<template id="commentCardTemplate">
<style>
:host{
...
}
.info{
...
}
...
</style>
<div class="info">
<div class="avatar">
<img src="" alt="" />
</div>
<div class="other">
<div class="nickname"></div>
<div class="time"></div>
</div>
</div>
<div class="text"></div>
</template>
`;
//这里借用的jq的方法,也可以用其他的替代
document.documentElement.appendChild($.html(tmpl)[0]);
class CommentCard extends HTMLElement{
constructor(){
super();
let templateEle = document.getElementById('commentCardTemplate');
let content = templateEle.content.cloneNode(true);
this.appendChild(content);
}
}
3. 内容的填充
至此,整个自定义标签的结构与样式已经构建完毕。
接下来就是内容的填充了。
我们可以把要填充的内容,作为元素的标签的一个属性写进去。
在 custom elements 类的构造函数中,可以通过getAttribute或者dataset方式获取,并填充在对应的位置。
<comment-card data-avatar="xxx" data-nickname="xxx"></comment-card>
class CommentCard extends HTMLElement{
constructor(){
super();
let templateEle = document.getElementById('commentCardTemplate');
let content = templateEle.content.cloneNode(true);
//内容填充
content.querySelector('img').src = this.dataset.avatar;
content.querySelector('.nickname').innerHTML = this.dataset.nickname;
this.appendChild(content);
}
}
4. 需要注意的几个点
- custom elements 的名称,不能是单个单词,其中必须要有短横线。
- 记得在样式中,给自定义元素的样式,增加
display
。最好是在自定义元素的定义中,写在:host
伪类内,让其默认拥有一个display
样式,将其设置为块状元素或者行内元素等。否则,元素将无法通过设置margin
等进行定位。 - 如果你把
template
元素内容,和类的定义都封装在同一个js文件中,记得通过(function(){})()
等方式为其设置一个单独的作用域,不然,等设置的自定义元素多了之后,会被变量的名称烦死。 - 一个奇怪的问题。custom elements 类的定义、自定义元素的注册等,要放在 DOM文档 自定义标签 加载后。比如将封装的js文件放在body标签后,或者使用
$.ready()
等方法执行类的定义、自定义元素的注册等。否则的话,DOM文档中自定义元素的attibute内容,在 custom elements 类的构造函数中无法读取出来,导致内容填充失败。
但是但是,通过js,将自定义元素添加进文档,就不存在问题。