Web Components实践开发Tab组件
本文是对web components的一次实践,最终目的是做出一个tab组件,本文涉及Custom Elements(自定义元素)、HTML Imports(HTML导入)、HTML Templates(HTML模板)、Shadow DOM(影子DOM)四部分知识。
自定义元素
自定义元素通过document.registerElement注册。
第一个参数是自定义元素的标签名,标签名需要使用 - 连接,之所以要这样设计是因为这样能使解析器能很容易的区分自定义元素和 HTML 规范定义的元素,同时确保了 HTML 增加新标签时的向前兼容。
第二个参数用于描述该元素的原型。
<my-tab></my-tab>
<script>
document.registerElement("my-tab",{
prototype:Object.create(HTMLElement.prototype)
});
</script>
虽然我们创建了一个自定义的元素,但它现在还没有任何内容,我们去添加点内容吧
<my-tab></my-tab>
<script>
document.registerElement("my-tab",{
prototype:Object.create(HTMLElement.prototype,{
createdCallback:{
value:function(){
var div = document.createElement("div");
div.textContent = "web compontents";
this.appendChild(div);
}
}
})
});
</script>
效果如下

在创建自定义元素时,会发生以下几个事件
| 自定义元素的生命周期 |
|---|
| createdCallback |
| enteredDocumentCallback |
| leftDocumentCallback |
| attributeChangedCallback(attrName, oldVal, newVal) |
以上代码的意思是,在元素创建完成时给当前自定义元素插入一个元素,this指向页面中的my-tab,这个my-tab的原型指向以下这个对象
Object.create(HTMLElement.prototype,{
createdCallback:{
value:function(){
var div = document.createElement("div");
div.textContent = "web compontents";
this.appendChild(div);
}
}
})
通过js来创建元素还是太麻烦了些,我们可以通过template模板来写,如下
<template id="tmp">
<style>
li{
list-style:none;
}
.tab-title{
display:flex;
}
.tab-title li{
width:100px;
line-height:35px;
text-align:center;
border:1px solid #ccc;
}
.tab-title li:not(:last-of-type){
border-right:none;
}
.tab-title .active{
color:orange;
}
.tab-content li{
display:none;
}
.tab-content .active{
display:block;
}
</style>
<ul class="tab-title">
<li class="active">标题1</li>
<li>标题2</li>
<li>标题3</li>
</ul>
<ul class="tab-content">
<li class="active">内容1</li>
<li>内容2</li>
<li>内容3</li>
</ul>
<script>
var titleNode = document.querySelector(".tab-title"),
titleNodes = titleNode.children,
contentNodes = document.querySelectorAll(".tab-content > li"),
preIndex = 0;
titleNode.addEventListener("click",function(event){
if(event.target.matches(".tab-title > li")){
var index = Array.prototype.indexOf.call(titleNodes,event.target);
titleNodes[preIndex].classList.remove("active");
titleNodes[index].classList.add("active");
contentNodes[preIndex].classList.remove("active");
contentNodes[index].classList.add("active");
preIndex = index;
}
});
</script>
</template>
<my-tab></my-tab>
<script>
document.registerElement("my-tab",{
prototype:Object.create(HTMLElement.prototype,{
createdCallback:{
value:function(){
var tmp = document.getElementById("tmp");
this.appendChild(tmp.content.cloneNode(true));
}
}
})
});
</script>
用template模板来写的好处显而易见,template模板的内容并不会直接显示在页面中,我们通过tmp.content.cloneNode(true)复杂了一份模板内容,将内容添加到了自定义元素中。效果如下

Shadow DOM
尽管现在已经实现了一个自定义元素,但是还有诸多的问题,如我们在template中写的样式依然会影响全局的,全局的也能影响我们自定义元素的样式,如果想要解决这个问题,我们需要使用到Shadow DOM,如果你对Shadow DOM不熟,强烈建议你看Shadow DOM系列,本文不做详细介绍。
<template id="tmp">
<style>
li{
list-style:none;
}
.tab-title{
display:flex;
}
.tab-title li{
width:100px;
line-height:35px;
text-align:center;
border:1px solid #ccc;
}
.tab-title li:not(:last-of-type){
border-right:none;
}
.tab-title .active{
color:orange;
}
.tab-content li{
display:none;
}
.tab-content .active{
display:block;
}
</style>
<ul class="tab-title">
<li class="active">标题1</li>
<li>标题2</li>
<li>标题3</li>
</ul>
<ul class="tab-content">
<li class="active">内容1</li>
<li>内容2</li>
<li>内容3</li>
</ul>
</template>
<my-tab></my-tab>
<my-tab></my-tab>
<script>
document.registerElement("my-tab",{
prototype:Object.create(HTMLElement.prototype,{
createdCallback:{
value:function(){
var tmp = document.getElementById("tmp");
var shadow = this.createShadowRoot();
shadow.appendChild(document.importNode(tmp.content,true));
var titleNode = shadow.querySelector(".tab-title"),
titleNodes = titleNode.children,
contentNodes = shadow.querySelectorAll(".tab-content > li"),
preIndex = 0;
titleNode.addEventListener("click",function(event){
if(event.target.matches(".tab-title > li")){
var index = Array.prototype.indexOf.call(titleNodes,event.target);
titleNodes[preIndex].classList.remove("active");
titleNodes[index].classList.add("active");
contentNodes[preIndex].classList.remove("active");
contentNodes[index].classList.add("active");
preIndex = index;
}
});
}
}
})
});
</script>
this.createShadowRoot()此句代码表示,将当前元素作为影子DOM的寄主,其他代码基本和之前的一样,不过得注意一下,不能去用document去获取影子DOM里面的元素了,需通过this.createShadowRoot();返回的对象去操作,效果如下

现在代码就互不影响啦,不过我们的HTML代码写的还是太死,我们再将代码改改
<template id="tmp">
<style>
li{
list-style:none;
}
.tab-title{
display:flex;
}
.tab-title li{
width:100px;
line-height:35px;
text-align:center;
border:1px solid #ccc;
}
.tab-title li:not(:last-of-type){
border-right:none;
}
.tab-title .active{
color:orange;
}
.tab-content li{
display:none;
}
.tab-content .active{
display:block;
}
</style>
<content select=".tab-title"></content>
<content select=".tab-content"></content>
</template>
<my-tab>
<ul class="tab-title">
<li class="active">标题1</li>
<li>标题2</li>
<li>标题3</li>
</ul>
<ul class="tab-content">
<li class="active">内容1</li>
<li>内容2</li>
<li>内容3</li>
</ul>
</my-tab>
content标签可以用来获取my-tab中的内容,select用来选择对应的内容,只要和class对应起来就行,我们来看看效果

啊,样式竟然不行了,主要是不能这么用了,给content中的元素设置样式得用::content,如下
<style>
::content li{
list-style:none;
}
::content .tab-title{
display:flex;
}
::content .tab-title li{
width:100px;
line-height:35px;
text-align:center;
border:1px solid #ccc;
}
::content .tab-title li:not(:last-of-type){
border-right:none;
}
::content .tab-title .active{
color:orange;
}
::content .tab-content li{
display:none;
}
::content .tab-content .active{
display:block;
}
</style>
效果如下

我们还得将js中一段话改改
var titleNode = this.querySelector(".tab-title"),
titleNodes = titleNode.children,
contentNodes = this.querySelectorAll(".tab-content > li"),
preIndex = 0;
前面我们用的是shadow来获取的元素,现在用的是content中的内容,那么获取元素和我们平常获取的方式一样。我猜,你肯定看蒙了,所以啊,还是先去看我前面推荐的那个Shadow Dom教程吧。
我们再添加一个tab
<my-tab>
<ul class="tab-title">
<li class="active">HTML</li>
<li>CSS</li>
<li>javascript</li>
</ul>
<ul class="tab-content">
<li class="active">HTMLHTMLHTMLHTML</li>
<li>CSSCSSCSSCSS</li>
<li>javascriptjavascriptjavascript</li>
</ul>
</my-tab>
效果如下

HTML导入
是不是感觉很强大,不过现在还没有完,一般我们组件都是放在一个文件里面的,需要的时候引进来进行,所以啊,我们还得干活,HTML的引入具体如下
<link id="tab" rel="import" href="tab.html">
将rel改成import就可以引入html文件了,我们将前面的所有代码都复制到tab.html中
tab.html
<template id="tmp">
<style>
::content li{
list-style:none;
}
::content .tab-title{
display:flex;
}
::content .tab-title li{
width:100px;
line-height:35px;
text-align:center;
border:1px solid #ccc;
}
::content .tab-title li:not(:last-of-type){
border-right:none;
}
::content .tab-title .active{
color:orange;
}
::content .tab-content li{
display:none;
}
::content .tab-content .active{
display:block;
}
</style>
<content select=".tab-title"></content>
<content select=".tab-content"></content>
</template>
<script>
document.registerElement("my-tab",{
prototype:Object.create(HTMLElement.prototype,{
createdCallback:{
value:function(){
var tmp = document.querySelector("#tab").import.querySelector("#tmp");
var shadow = this.createShadowRoot();
shadow.appendChild(document.importNode(tmp.content,true));
var titleNode = this.querySelector(".tab-title"),
titleNodes = titleNode.children,
contentNodes = this.querySelectorAll(".tab-content > li"),
preIndex = 0;
titleNode.addEventListener("click",function(event){
if(event.target.matches(".tab-title > li")){
var index = Array.prototype.indexOf.call(titleNodes,event.target);
titleNodes[preIndex].classList.remove("active");
titleNodes[index].classList.add("active");
contentNodes[preIndex].classList.remove("active");
contentNodes[index].classList.add("active");
preIndex = index;
}
});
}
}
})
});
</script>
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link id="tab" rel="import" href="tab.html">
<script src="index2.js" defer></script>
</head>
<body>
<my-tab>
<ul class="tab-title">
<li class="active">标题1</li>
<li>标题2</li>
<li>标题3</li>
</ul>
<ul class="tab-content">
<li class="active">内容1</li>
<li>内容2</li>
<li>内容3</li>
</ul>
</my-tab>
<my-tab>
<ul class="tab-title">
<li class="active">HTML</li>
<li>CSS</li>
<li>javascript</li>
</ul>
<ul class="tab-content">
<li class="active">HTMLHTMLHTMLHTML</li>
<li>CSSCSSCSSCSS</li>
<li>javascriptjavascriptjavascript</li>
</ul>
</my-tab>
</body>
</html>
效果如下

如果你有去看前面的代码的话,和现在的对吧会发现有一段代码被我改了,tab.html中的var tmp = document.querySelector("#tab").import.querySelector("#tmp");这句,之前是直接通过document来获取的template模板,但现在有些不同,虽然我们的代码是通过html导入过来的,但是tab中的document仍然还是主页面的document,因此我们还得通过document.querySelector("#tab").import来获取模板元素,具体可以到网上搜索一下。
到这里总算是完成了这个tab组件了,这里不得不说一句,关于组件中的javascript根本没有被分离,它的作用域仍然是全局的,有必要的话请使用自执行函数。
Web Components实践开发Tab组件的更多相关文章
- Svelte入门——Web Components实现跨框架组件复用
Svelte 是构建 Web 应用程序的一种新方法,推出后一直不温不火,没有继Angular.React和VUE成为第四大框架,但也没有失去热度,无人问津.造成这种情况很重要的一个原因是,Svelte ...
- Svelte入门——Web Components实现跨框架组件复用(二)
在上节中,我们一起了解了如何使用Svelte封装Web Component,从而实现在不同页面间使用电子表格组件. Svelte封装组件跨框架复用,带来的好处也十分明显: 1.使用框架开发,更容易维护 ...
- Web Components之Custom Elements
什么是Web Component? Web Components 包含了多种不同的技术.你可以把Web Components当做是用一系列的Web技术创建的.可重用的用户界面组件的统称.Web Com ...
- React文档(二十三)Web Components
React和web components是为了解决不同问题而创立的.web components为可重用组件提供了健壮的封装,而React提供了声明式的库来保持DOM和数据同步.这两点是互相补充的.作 ...
- Web Components(续)
概述 之前我们介绍了Web Components的基本概念,现在我们给出一个使用Web Components的实例代码,并且对组件化进行一些思考.记录下来,供以后开发时参考,相信对其他人也有用. 实例 ...
- Fiori Fundamentals和SAP UI5 Web Components
这周有位同事邀请我给团队讲一讲SAP技术的演进历史,所以我准备了下面几个主题来介绍. 其中SAP的技术回顾和演进,我的思路就是从前后台两方面分别介绍. 我画了一张非常简单的图: 去年5月我写过一篇文章 ...
- 可选的Web Components类库
首先需要说明的是这不是一篇 Web Components 的科普文章,如果对此了解不多推荐先读<A Guide to Web Components>. 有句古话-“授人以鱼,不如授人以渔” ...
- 推荐使用Tiny Framework web开发UI组件
TINY FRAMEWORK 基于组件化的J2EE开发框架,from:http://www.tinygroup.org/ 名字 Tiny名称的来历 取名Tiny是取其微不足道,微小之意. Tiny ...
- 腾讯发布新版前端组件框架 Omi,全面拥抱 Web Components
Omi - 合一 下一代 Web 框架,去万物糟粕,合精华为一 → https://github.com/Tencent/omi 特性 4KB 的代码尺寸,比小更小 顺势而为,顺从浏览器的发展和 AP ...
随机推荐
- POJ2429--GCD & LCM Inverse (UNSOLVED)
Given two positive integers a and b, we can easily calculate the greatest common divisor (GCD) and t ...
- 2016-2017-2 20155326实验二《Java面向对象程序设计》实验报告
2016-2017-2 20155326实验二<Java面向对象程序设计>实验报告 实验内容 初步掌握单元测试和TDD 理解并掌握面向对象三要素:封装.继承.多态 初步掌握UML建模 熟悉 ...
- [ 9.12 ]CF每日一题系列—— 960B暴力数组
Description: 给你两个数组,顺序一定,问你第一个数组连续的几个值等于下一个数组连续的几个值,然后寻找这个值得最大值,也就是满足就换 Solution: 用两个变量索引,判断即可 #incl ...
- HTTP协议(一)基本概念、HTTP方法、HTTP状态码
1.HTTP是什么? 它的全名叫:HyperText Transfer Protocol ,中国名字:超文本传输协议 : 它是可靠的数据传输协议,是Internet 的多媒体信使,即使数据相隔天涯海角 ...
- .net图表之ECharts随笔02-字符云
后续每一类图表,若无特殊说明,都将建立在01的基础上,修改参数option,且参数均以json的格式 要形成如图所示的字符云,一般需要设置两个大参数——title和series 其中,title就是图 ...
- ASP.NET MVC NPOI导入Excel DataTable批量导入到数据库
使用NPOI导入Excel 首先在MVC项目中导入NPOI 查询NPOI安装,排序依据,选择:最高下载量,选择第一个. 在控制器中创建ExcelController 在Index视图中写入代码: @u ...
- 简单的C# Socket通信实例
一.套接字(socket)概念 套接字(socket)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元.它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本 ...
- jdk在Linux下的安装
之前学linux的时候,也是赶鸭子上架,照着教程一步一步的走,但是今天linux出现了点问题重装一下 重新学习一些linux下常需软件的安装 一般我们再装完系统后,装的匆忙,先把ip搞出来 切换到该目 ...
- pcre库
pcre : perl compatible regular expressions , perl 兼容正则表达式 www.pcre.org 按装pcre是为了使Nginx支持具备URI重写功能的 ...
- @transactional注解,报错后数据库操作回滚失败
1. https://jingyan.baidu.com/article/3a2f7c2e27d51b26afd611ff.html 2. https://blog.csdn.net/lee_star ...