本文是对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. redis简单使用

    主要参考资料:http://wiki.jikexueyuan.com/project/redis-guide/data-type.html一.redis 安装1.在官网下载安装包2.解压安装包 tar ...

  2. idea2018.2.4的安装激活与热部署插件JRebel的激活方法

    去Idea的官网下载如上版本的Idea安装文件 并且在网上搜索下载如下破解工具 放置在相应的Idea安装目录下 然后在Idea中输入激活码 { "licenseId": " ...

  3. Mahout简介

    Mahout简介 一.mahout是什么 Apache Mahout是ApacheSoftware Foundation (ASF)旗下的一个开源项目,提供了一些经典的机器学习的算法,皆在帮助开发人员 ...

  4. hive-内部表和外部表 对比

    建表时,需要考虑究竟建内部表还是外部表,内部表和外部表都有哪些不同? 内部表: 1. 数据存储位置:数据最终会被移动到 hive.metastore.warehouse.dir指定的路径下,以表名创建 ...

  5. 2015-2016-1 学期《软件工程》学生名单-- PS:教材使用《构建之法》第二版 --邹欣著

    1208053044 王威 男 1313023001 饶阳梅 女 1313023002 应蕾蕾 女 1313023004 袁立萍 女 1313023005 黎洋阳 女 1313023006 蒋欣 女 ...

  6. 使用命令行打包 nuget 包

    对于那些不打算涉及这么复杂而又想制作自己的 nuget 包的园友们,我是推荐使用 Nuget Package Explorer 来制作的.关于这个图形化的 nuget 包管理软件的使用,博客园内有相关 ...

  7. [SDOI2011]消耗战(虚树+树形动规)

    虚树dp 虚树的主要思想: 不遍历没用的的节点以及没用的子树,从而使复杂度降低到\(\sum\limits k\)(k为询问的节点的总数). 所以怎么办: 只把询问节点和其LCA放入询问的数组中. 1 ...

  8. 移动端font-size适配方案(续)

    概述 之前写过一篇移动端font-size适配方案,但是在实践过程中,还是发现当时的思维太局限了,视野太窄了,所以现在补充更新一下,记录下来,供以后开发时参考,相信对其他人也有用. 我上一篇博文主要有 ...

  9. JS 跨域认识及如何解决

    什么是跨域 指的是浏览器不允许javascrip脚本向其他域名发起ajax请求. 跨域的各种情况判定 URL 说明 是否允许通信 http://www.a.com/a.js http://www.a. ...

  10. python并发编程之进程池,线程池concurrent.futures

    进程池与线程池 在刚开始学多进程或多线程时,我们迫不及待地基于多进程或多线程实现并发的套接字通信,然而这种实现方式的致命缺陷是:服务的开启的进程数或线程数都会随着并发的客户端数目地增多而增多, 这会对 ...