本文是对web components的一次实践,最终目的是做出一个tab组件,本文涉及Custom Elements(自定义元素)、HTML Imports(HTML导入)、HTML Templates(HTML模板)、Shadow DOM(影子DOM)四部分知识。

自定义元素

自定义元素通过document.registerElement注册。

第一个参数是自定义元素的标签名,标签名需要使用 - 连接,之所以要这样设计是因为这样能使解析器能很容易的区分自定义元素和 HTML 规范定义的元素,同时确保了 HTML 增加新标签时的向前兼容。

第二个参数用于描述该元素的原型。

  1. <my-tab></my-tab>
  2. <script>
  3. document.registerElement("my-tab",{
  4. prototype:Object.create(HTMLElement.prototype)
  5. });
  6. </script>

虽然我们创建了一个自定义的元素,但它现在还没有任何内容,我们去添加点内容吧

  1. <my-tab></my-tab>
  2. <script>
  3. document.registerElement("my-tab",{
  4. prototype:Object.create(HTMLElement.prototype,{
  5. createdCallback:{
  6. value:function(){
  7. var div = document.createElement("div");
  8. div.textContent = "web compontents";
  9. this.appendChild(div);
  10. }
  11. }
  12. })
  13. });
  14. </script>

效果如下

在创建自定义元素时,会发生以下几个事件

自定义元素的生命周期
createdCallback
enteredDocumentCallback
leftDocumentCallback
attributeChangedCallback(attrName, oldVal, newVal)

以上代码的意思是,在元素创建完成时给当前自定义元素插入一个元素,this指向页面中的my-tab,这个my-tab的原型指向以下这个对象

  1. Object.create(HTMLElement.prototype,{
  2. createdCallback:{
  3. value:function(){
  4. var div = document.createElement("div");
  5. div.textContent = "web compontents";
  6. this.appendChild(div);
  7. }
  8. }
  9. })

通过js来创建元素还是太麻烦了些,我们可以通过template模板来写,如下

  1. <template id="tmp">
  2. <style>
  3. li{
  4. list-style:none;
  5. }
  6. .tab-title{
  7. display:flex;
  8. }
  9. .tab-title li{
  10. width:100px;
  11. line-height:35px;
  12. text-align:center;
  13. border:1px solid #ccc;
  14. }
  15. .tab-title li:not(:last-of-type){
  16. border-right:none;
  17. }
  18. .tab-title .active{
  19. color:orange;
  20. }
  21. .tab-content li{
  22. display:none;
  23. }
  24. .tab-content .active{
  25. display:block;
  26. }
  27. </style>
  28. <ul class="tab-title">
  29. <li class="active">标题1</li>
  30. <li>标题2</li>
  31. <li>标题3</li>
  32. </ul>
  33. <ul class="tab-content">
  34. <li class="active">内容1</li>
  35. <li>内容2</li>
  36. <li>内容3</li>
  37. </ul>
  38. <script>
  39. var titleNode = document.querySelector(".tab-title"),
  40. titleNodes = titleNode.children,
  41. contentNodes = document.querySelectorAll(".tab-content > li"),
  42. preIndex = 0;
  43. titleNode.addEventListener("click",function(event){
  44. if(event.target.matches(".tab-title > li")){
  45. var index = Array.prototype.indexOf.call(titleNodes,event.target);
  46. titleNodes[preIndex].classList.remove("active");
  47. titleNodes[index].classList.add("active");
  48. contentNodes[preIndex].classList.remove("active");
  49. contentNodes[index].classList.add("active");
  50. preIndex = index;
  51. }
  52. });
  53. </script>
  54. </template>
  55. <my-tab></my-tab>
  56. <script>
  57. document.registerElement("my-tab",{
  58. prototype:Object.create(HTMLElement.prototype,{
  59. createdCallback:{
  60. value:function(){
  61. var tmp = document.getElementById("tmp");
  62. this.appendChild(tmp.content.cloneNode(true));
  63. }
  64. }
  65. })
  66. });
  67. </script>

用template模板来写的好处显而易见,template模板的内容并不会直接显示在页面中,我们通过tmp.content.cloneNode(true)复杂了一份模板内容,将内容添加到了自定义元素中。效果如下

Shadow DOM

尽管现在已经实现了一个自定义元素,但是还有诸多的问题,如我们在template中写的样式依然会影响全局的,全局的也能影响我们自定义元素的样式,如果想要解决这个问题,我们需要使用到Shadow DOM,如果你对Shadow DOM不熟,强烈建议你看Shadow DOM系列,本文不做详细介绍。

  1. <template id="tmp">
  2. <style>
  3. li{
  4. list-style:none;
  5. }
  6. .tab-title{
  7. display:flex;
  8. }
  9. .tab-title li{
  10. width:100px;
  11. line-height:35px;
  12. text-align:center;
  13. border:1px solid #ccc;
  14. }
  15. .tab-title li:not(:last-of-type){
  16. border-right:none;
  17. }
  18. .tab-title .active{
  19. color:orange;
  20. }
  21. .tab-content li{
  22. display:none;
  23. }
  24. .tab-content .active{
  25. display:block;
  26. }
  27. </style>
  28. <ul class="tab-title">
  29. <li class="active">标题1</li>
  30. <li>标题2</li>
  31. <li>标题3</li>
  32. </ul>
  33. <ul class="tab-content">
  34. <li class="active">内容1</li>
  35. <li>内容2</li>
  36. <li>内容3</li>
  37. </ul>
  38. </template>
  39. <my-tab></my-tab>
  40. <my-tab></my-tab>
  41. <script>
  42. document.registerElement("my-tab",{
  43. prototype:Object.create(HTMLElement.prototype,{
  44. createdCallback:{
  45. value:function(){
  46. var tmp = document.getElementById("tmp");
  47. var shadow = this.createShadowRoot();
  48. shadow.appendChild(document.importNode(tmp.content,true));
  49. var titleNode = shadow.querySelector(".tab-title"),
  50. titleNodes = titleNode.children,
  51. contentNodes = shadow.querySelectorAll(".tab-content > li"),
  52. preIndex = 0;
  53. titleNode.addEventListener("click",function(event){
  54. if(event.target.matches(".tab-title > li")){
  55. var index = Array.prototype.indexOf.call(titleNodes,event.target);
  56. titleNodes[preIndex].classList.remove("active");
  57. titleNodes[index].classList.add("active");
  58. contentNodes[preIndex].classList.remove("active");
  59. contentNodes[index].classList.add("active");
  60. preIndex = index;
  61. }
  62. });
  63. }
  64. }
  65. })
  66. });
  67. </script>

this.createShadowRoot()此句代码表示,将当前元素作为影子DOM的寄主,其他代码基本和之前的一样,不过得注意一下,不能去用document去获取影子DOM里面的元素了,需通过this.createShadowRoot();返回的对象去操作,效果如下

现在代码就互不影响啦,不过我们的HTML代码写的还是太死,我们再将代码改改

  1. <template id="tmp">
  2. <style>
  3. li{
  4. list-style:none;
  5. }
  6. .tab-title{
  7. display:flex;
  8. }
  9. .tab-title li{
  10. width:100px;
  11. line-height:35px;
  12. text-align:center;
  13. border:1px solid #ccc;
  14. }
  15. .tab-title li:not(:last-of-type){
  16. border-right:none;
  17. }
  18. .tab-title .active{
  19. color:orange;
  20. }
  21. .tab-content li{
  22. display:none;
  23. }
  24. .tab-content .active{
  25. display:block;
  26. }
  27. </style>
  28. <content select=".tab-title"></content>
  29. <content select=".tab-content"></content>
  30. </template>
  31. <my-tab>
  32. <ul class="tab-title">
  33. <li class="active">标题1</li>
  34. <li>标题2</li>
  35. <li>标题3</li>
  36. </ul>
  37. <ul class="tab-content">
  38. <li class="active">内容1</li>
  39. <li>内容2</li>
  40. <li>内容3</li>
  41. </ul>
  42. </my-tab>

content标签可以用来获取my-tab中的内容,select用来选择对应的内容,只要和class对应起来就行,我们来看看效果

啊,样式竟然不行了,主要是不能这么用了,给content中的元素设置样式得用::content,如下

  1. <style>
  2. ::content li{
  3. list-style:none;
  4. }
  5. ::content .tab-title{
  6. display:flex;
  7. }
  8. ::content .tab-title li{
  9. width:100px;
  10. line-height:35px;
  11. text-align:center;
  12. border:1px solid #ccc;
  13. }
  14. ::content .tab-title li:not(:last-of-type){
  15. border-right:none;
  16. }
  17. ::content .tab-title .active{
  18. color:orange;
  19. }
  20. ::content .tab-content li{
  21. display:none;
  22. }
  23. ::content .tab-content .active{
  24. display:block;
  25. }
  26. </style>

效果如下

我们还得将js中一段话改改

  1. var titleNode = this.querySelector(".tab-title"),
  2. titleNodes = titleNode.children,
  3. contentNodes = this.querySelectorAll(".tab-content > li"),
  4. preIndex = 0;

前面我们用的是shadow来获取的元素,现在用的是content中的内容,那么获取元素和我们平常获取的方式一样。我猜,你肯定看蒙了,所以啊,还是先去看我前面推荐的那个Shadow Dom教程吧。

我们再添加一个tab

  1. <my-tab>
  2. <ul class="tab-title">
  3. <li class="active">HTML</li>
  4. <li>CSS</li>
  5. <li>javascript</li>
  6. </ul>
  7. <ul class="tab-content">
  8. <li class="active">HTMLHTMLHTMLHTML</li>
  9. <li>CSSCSSCSSCSS</li>
  10. <li>javascriptjavascriptjavascript</li>
  11. </ul>
  12. </my-tab>

效果如下

HTML导入

是不是感觉很强大,不过现在还没有完,一般我们组件都是放在一个文件里面的,需要的时候引进来进行,所以啊,我们还得干活,HTML的引入具体如下

  1. <link id="tab" rel="import" href="tab.html">

将rel改成import就可以引入html文件了,我们将前面的所有代码都复制到tab.html中

tab.html
  1. <template id="tmp">
  2. <style>
  3. ::content li{
  4. list-style:none;
  5. }
  6. ::content .tab-title{
  7. display:flex;
  8. }
  9. ::content .tab-title li{
  10. width:100px;
  11. line-height:35px;
  12. text-align:center;
  13. border:1px solid #ccc;
  14. }
  15. ::content .tab-title li:not(:last-of-type){
  16. border-right:none;
  17. }
  18. ::content .tab-title .active{
  19. color:orange;
  20. }
  21. ::content .tab-content li{
  22. display:none;
  23. }
  24. ::content .tab-content .active{
  25. display:block;
  26. }
  27. </style>
  28. <content select=".tab-title"></content>
  29. <content select=".tab-content"></content>
  30. </template>
  31. <script>
  32. document.registerElement("my-tab",{
  33. prototype:Object.create(HTMLElement.prototype,{
  34. createdCallback:{
  35. value:function(){
  36. var tmp = document.querySelector("#tab").import.querySelector("#tmp");
  37. var shadow = this.createShadowRoot();
  38. shadow.appendChild(document.importNode(tmp.content,true));
  39. var titleNode = this.querySelector(".tab-title"),
  40. titleNodes = titleNode.children,
  41. contentNodes = this.querySelectorAll(".tab-content > li"),
  42. preIndex = 0;
  43. titleNode.addEventListener("click",function(event){
  44. if(event.target.matches(".tab-title > li")){
  45. var index = Array.prototype.indexOf.call(titleNodes,event.target);
  46. titleNodes[preIndex].classList.remove("active");
  47. titleNodes[index].classList.add("active");
  48. contentNodes[preIndex].classList.remove("active");
  49. contentNodes[index].classList.add("active");
  50. preIndex = index;
  51. }
  52. });
  53. }
  54. }
  55. })
  56. });
  57. </script>
index.html
  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Document</title>
  6. <link id="tab" rel="import" href="tab.html">
  7. <script src="index2.js" defer></script>
  8. </head>
  9. <body>
  10. <my-tab>
  11. <ul class="tab-title">
  12. <li class="active">标题1</li>
  13. <li>标题2</li>
  14. <li>标题3</li>
  15. </ul>
  16. <ul class="tab-content">
  17. <li class="active">内容1</li>
  18. <li>内容2</li>
  19. <li>内容3</li>
  20. </ul>
  21. </my-tab>
  22. <my-tab>
  23. <ul class="tab-title">
  24. <li class="active">HTML</li>
  25. <li>CSS</li>
  26. <li>javascript</li>
  27. </ul>
  28. <ul class="tab-content">
  29. <li class="active">HTMLHTMLHTMLHTML</li>
  30. <li>CSSCSSCSSCSS</li>
  31. <li>javascriptjavascriptjavascript</li>
  32. </ul>
  33. </my-tab>
  34. </body>
  35. </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. js基础学习笔记(三)

    3.1 认识DOM 文档对象模型DOM(Document Object Model)定义访问和处理HTML文档的标准方法.DOM 将HTML文档呈现为带有元素.属性和文本的树结构(节点树). 先来看看 ...

  2. golang web sample

    一.学习想法 用两天的时间学习golang,但这次是先不看书的,直接写代码先. 我们常习惯边看书边学习写代码,但发现过程是比较缓慢的,所以我就先想写代码, 边写边查.就我们所知,web app一般是基 ...

  3. eclipse/sublime 等宽字体设置

    转载请注明出处:http://www.cnblogs.com/wubdut/p/4621889.html 使用ubuntu14.04会产生很多想日犬的地方.大家一般习惯于使用 eclipse 进行 j ...

  4. ubuntu apache linux

    在ubuntu下安装的apache, 很多配置文件都分开写了,需要了解一下各部分: http://blog.csdn.net/veizz/article/details/7410784 Ubuntu下 ...

  5. mac上配置php开发环境

    玉忠之前在我的mac上配置过,当时项目不能区分大小写,所以就没成功,我现在在他得基础上继续配置,希望成功. 教程:http://my.oschina.net/joanfen/blog/171109 以 ...

  6. bootstrap2.2登录验证

    <!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>& ...

  7. hdu4048

    题意:给定m个数,还有n,n表示有一个长度为n的环,现在要求从M个数中选出若干个数,要求选出的数最大公约数为1,填充在n个位置中,选出的数可以重复,求多少种种方案.旋转当成一样的 . 思路:假设现在选 ...

  8. 量子力学与广义相对论的统一——用广义相对论解释海森堡测不准原理 Unification of Quantum Mechanics and General Relativity: Explaining Heisenberg Uncertainty Principle with General Relativity

    从海森堡测不准原理的实验开始: 从实验中可以看到,当有光源测定路线,且双孔打开的时候,接收板原波谷处变成了波峰. 对此,广义相对论的解释是:此时电子经过双孔后的轨迹发生了变化.双孔周围的空间弯曲度被光 ...

  9. [C#]剖析异步编程语法糖: async和await

    一.难以被接受的async 自从C#5.0,语法糖大家庭又加入了两位新成员: async和await. 然而从我知道这两个家伙之后的很长一段时间,我甚至都没搞明白应该怎么使用它们,这种全新的异步编程模 ...

  10. C++动态(显式)调用 C++ dll

    1.创建DLL新项目Dll1,Dll1.cpp: extern "C" __declspec(dllexport) const char* myfunc() { return &q ...