本文是对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组件的更多相关文章

  1. Svelte入门——Web Components实现跨框架组件复用

    Svelte 是构建 Web 应用程序的一种新方法,推出后一直不温不火,没有继Angular.React和VUE成为第四大框架,但也没有失去热度,无人问津.造成这种情况很重要的一个原因是,Svelte ...

  2. Svelte入门——Web Components实现跨框架组件复用(二)

    在上节中,我们一起了解了如何使用Svelte封装Web Component,从而实现在不同页面间使用电子表格组件. Svelte封装组件跨框架复用,带来的好处也十分明显: 1.使用框架开发,更容易维护 ...

  3. Web Components之Custom Elements

    什么是Web Component? Web Components 包含了多种不同的技术.你可以把Web Components当做是用一系列的Web技术创建的.可重用的用户界面组件的统称.Web Com ...

  4. React文档(二十三)Web Components

    React和web components是为了解决不同问题而创立的.web components为可重用组件提供了健壮的封装,而React提供了声明式的库来保持DOM和数据同步.这两点是互相补充的.作 ...

  5. Web Components(续)

    概述 之前我们介绍了Web Components的基本概念,现在我们给出一个使用Web Components的实例代码,并且对组件化进行一些思考.记录下来,供以后开发时参考,相信对其他人也有用. 实例 ...

  6. Fiori Fundamentals和SAP UI5 Web Components

    这周有位同事邀请我给团队讲一讲SAP技术的演进历史,所以我准备了下面几个主题来介绍. 其中SAP的技术回顾和演进,我的思路就是从前后台两方面分别介绍. 我画了一张非常简单的图: 去年5月我写过一篇文章 ...

  7. 可选的Web Components类库

    首先需要说明的是这不是一篇 Web Components 的科普文章,如果对此了解不多推荐先读<A Guide to Web Components>. 有句古话-“授人以鱼,不如授人以渔” ...

  8. 推荐使用Tiny Framework web开发UI组件

    TINY FRAMEWORK 基于组件化的J2EE开发框架,from:http://www.tinygroup.org/   名字 Tiny名称的来历 取名Tiny是取其微不足道,微小之意. Tiny ...

  9. 腾讯发布新版前端组件框架 Omi,全面拥抱 Web Components

    Omi - 合一 下一代 Web 框架,去万物糟粕,合精华为一 → https://github.com/Tencent/omi 特性 4KB 的代码尺寸,比小更小 顺势而为,顺从浏览器的发展和 AP ...

随机推荐

  1. 21069207《Linux内核原理与分析》第四周作业

    本周首先我结合mooc网的视频资料,了解了计算机的三大法宝,分别是存储程序计算机,函数调用堆栈和中断机制. 在计算机的三大法宝中,存储程序计算机是根本,函数调用堆栈是高级语言运行的基础,而中断机制成就 ...

  2. POJ3189 Steady Cow Assignment

    Steady Cow Assignment Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 6817   Accepted:  ...

  3. ZUFE2483 DO IT YOURSELF 2017-05-31 14:41 40人阅读 评论(0) 收藏

    2483: DO IT YOURSELF 时间限制: 2 Sec  内存限制: 128 MB 提交: 8  解决: 3 [提交][状态][讨论版] 题目描述 有四个字符串S,T,tmp,ans,一开始 ...

  4. NSString copy && strong

    http://www.cocoachina.com/ios/20150512/11805.html 我们在声明一个NSString属性时,对于其内存相关特性,通常有两种选择(基于ARC环境):stro ...

  5. vnc搭建

    PS:偶以前基本不用Linux的远程图形桌面,前几天有开发的同事配置CentOS的vnc有问题,找我解决,就顺便记录总结一下,这个总结是比较完整的.下面的配置在CentOS5.x和6.x.SUSE企业 ...

  6. 1.mybatis入门

    一:创建表 CREATE TABLE `country` ( `id` ) NOT NULL AUTO_INCREMENT, `countryname` varchar() DEFAULT NULL, ...

  7. 缓存中使用的ReentrantReadWriteLock锁

    java中提供了lock锁,简便了设计缓存,下面程序主要是使用读写锁的应用.... import java.util.HashMap; import java.util.Map; import jav ...

  8. android.os.NetworkOnMainThreadException的解决方案

    06-24 18:04:36.857: E/AndroidRuntime(22251): FATAL EXCEPTION: main 06-24 18:04:36.857: E/AndroidRunt ...

  9. 彻底卸载Oracle database 12c教程

    1.WIN+R,然后输入regedit,回车:2.在注册表中,进入目录:\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services,删除所有以oracl ...

  10. ASP.NET SignalR Troubeshooting

    method could not be resolved 场景: Javascript客户端不生成代理,调用服务端方法. 按照官网文档的用法: contosoChatHubProxy.invoke(' ...