本文是对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. Forward团队-爬虫豆瓣top250项目-需求分析

    一. 需求:1.爬取豆瓣电影top250. 2.获取电影名称,排名,分数,简介,导演,演员. 3.将爬取到的数据保存,以便随时查看. 3.可以将获取到的数据展示给用户. 二. 参考: 豆瓣api参考资 ...

  2. hibernate之查询

    Query对象 方便的对数据库和持久化对象进行查询,两种表达方式:HQL和SQL; Query经常用来绑定查询参数,限制查询条数.并最终执行查询语句. HQL 查询一个简单类(查询所有) @Test ...

  3. redis解决保存快照失败后redis无法写入的问题

    通过关闭配置项stop-writes-on-bgsave-error解决该问题. redis 127.0.0.1:6379> config set stop-writes-on-bgsave-e ...

  4. unigui web app之菜单

    unigui web app之菜单 拖放TUnimMenu控件到窗体上. side:=msleft 表示将在左边显示菜单. 菜单项属性 UnimMenu1.Visible := True;显示菜单 U ...

  5. acdream 20140730 D题

    今天见识到了“数学上来先打表”............ #include<iostream> using namespace std; #include<iomanip> #d ...

  6. C#Winfrom数据库读取日期(年月日格式转换)

    显示类型:2018-6-1 //说明:data_time.Text 控件名文本值 :dataset.Tables[0].Rows[0]["art_time"]  数据集.表名.[行 ...

  7. 【HDU5730】 Shell Necklace

    HDU5730 Shell Necklace 题目大意 已知连续i(1<=i<=n)个贝壳组合成一段项链的方案数a[i],求组合成包含n个贝壳的项链的总方案数. Solution cdq分 ...

  8. Linux系统VIM编辑器管理(2)

    VI/VIM模式概述 在 Linux 的世界中,绝大部分的配置文件都是以 ASCII 的纯文本形态存在,因此利用简单的文字编辑软件就能够修改设定了,与微软的 Windows 系统不同的是,如果你用惯了 ...

  9. git add .添加不成功

    情景: 我首先在一个有许多文件的文件夹中  git init  创建一个git管理仓库 之后 git add . 之后 git commit -m "提交" 发现提交不成功,文件没 ...

  10. Redis---基础数据结构

    1.String(字符串) 1.1 概述 字符串 string 是 Redis 最简单的数据结构.Redis 所有的数据结构都是以唯一的 key 字符串作为名称,然后通过这个唯一 key 值来获取相应 ...