首页 > 代码库 > web components折腾记
web components折腾记
了解web组件化开发是最初是从了解reactjs开始,但是一直对框架有抵触情绪,另外喜欢不走寻常路,喜欢简单好用的东西,越简单越好,进而开始研究web components。
web components这个技术因为太新,浏览器的支持还不完善,还没流行,也没啥中文资料参考,就是官方英文网站貌似都没看到有文档说明,折腾起来甚是费劲。
最开始对web components技术还很懵懂,只知道它由几个子技术组成,包括Custom Elements和Shadow DOM还有HTML Imports等等,于是拿着Custom Elements就开始半知半解的写代码,以为发现了新大陆,惊喜万分,因为没有了解Shadow DOM,所以对自定义标签里面的内容封装完全是自己在胡乱拼凑,里面对元素子节点的处理甚是丑陋,甚至还专门自己包装innerHTML的获取和设置。在自定义元素里面只包含原生元素的时候,勉强工作正常,但是一旦两个以上的自定义元素嵌套使用,就开始失灵了,存在解析之后重复嵌套的问题,最后dom结构相当冗余重复,甚至还萌生出自己写代码解析dom结构的想法。从惊喜万分变成信心被重挫。
自从买了本书认真啃一天之后,对它有了重新的认识,惊喜回来了,感叹技术的最高境界就是制定标准。
这里做一下简单的备忘:
组成web components技术的4部分:模板元素(template标签)、自定义元素(Custom Elements)、影子节点(Shadow DOM)还有html引入(HTML Imports)。
template标签具有惰性,本身不会被html解析影响文档,只有它的结构被附加到真实的节点上才会影响文档,里面可以写style 还有script,style里面的css不会影响布局,script里面的脚本不会被执行,并且因为惰性,只能是内联的,不能是外部引入的。template标签对应的js对象模型有个content属性,是包含所有子节点的DocumentFragment对象。
自定义元素通过document.createElement方法来创建自定义的元素,第一个参数是元素名字,w3c规范规定必须以减号分隔,防止和原生的元素名冲突,第二个元素时元素配置对象,可以指定元素的原型(一般继承自HTMLElement)和元素各个生命周期(createdCallback、attachedCallback、detachedCallback、attributeChangedCallback)的行为。
影子节点是重点,通过节点的createShadowRoot方法创建影子根,这个虚拟的节点里面所有内容都不会在主文档里面出现,和主文档隔离,里面的结构、样式完全不影响主文档,id都可以和主文档节点中的id重名。另外还有个关键的技术,把自定义节点的内容通过content标签映射到影子根里面。
html引入使引入组件不再麻烦,传统的引入需要单独引入css和js,html引入用link标签直接引入html,一个标签就可以引入一个组件,不管你又多少css和js文件。如此一来,感觉webpack等打包程序将来可能都用不上了,用不着费尽心力的把css处理成js文件。link标签一旦引入的是html文件,那么它的js对象模型里面就有个import属性,指向它引入的html的document对象。引入文档里面的css和js和主文档是共享同一个作用域的,dom节点树不是,是独立的。所以在引入html中写css和js要注意命名空间的干净。
这样一来就可以随心所欲的写自定义元素了,附上一个例子:
主文件(web components.html):
<!DOCTYPE html><html lang="zh"><head><title>web components demo</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta http-equiv="Pragma" content="no-cache"><meta http-equiv="Cache-Control" content="no-cache"><meta http-equiv="Expires" content="0"><meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0"><link rel="import" href="widget.html"/></head><body><center-middle><o-loading>加载中</o-loading></center-middle></body></html>
引入文件widget.html:
<template id="center-middle"> <style> :host { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.4); z-index: 1; display: block; } </style> <table style="width:100%;height: 100%;border-collapse: collapse;table-layout: auto;border: 0;"> <tr style="width:100%;height: 100%;"> <td style="width:100%;height: 100%;text-align: center;overflow: hidden;"> <!-- text-align: initial 还原成默认的值 --> <div style="display:inline-block;vertical-align:middle;text-align: initial;"> <content></content> </div> </td> </tr> </table></template><template id="o-loading"> <style> @-webkit-keyframes loading { 0% { -webkit-transform: rotate(0deg); } 100% { -webkit-transform: rotate(360deg); } } @keyframes loading { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .loading { box-sizing: border-box; border: 0.5em solid #666; border-top-color: #fff; border-radius: 2em; width: 4em; height: 4em; display: inline-block; -webkit-animation: loading 1s linear 0.00001ms infinite normal; animation: loading 1s linear 0.00001ms infinite normal; } </style> <i class="loading"> </i> <center> <content></content> <center></template><script> var parentDocument = document; var importDocument = parentDocument.currentScript.ownerDocument; parentDocument.registerElement("center-middle",{ "prototype":Object.create(HTMLElement.prototype,{ "createdCallback": { "value":function (){ var content = parentDocument.importNode(importDocument.getElementById("center-middle").content,true); this.createShadowRoot().appendChild(content); } } }) }); parentDocument.registerElement("o-loading",{ "prototype":Object.create(HTMLElement.prototype,{ "createdCallback": { "value":function (){ var content = parentDocument.importNode(importDocument.getElementById("o-loading").content,true); this.createShadowRoot().appendChild(content); } } }) });</script>
效果图:
一个居中的loading
兼容性测试:
检测浏览器对各种技术的支持情况的代码:
document.write("template:" + ("content" in document.createElement("template"))+"<br/>");
document.write("createShadowRoot:" + ("createShadowRoot" in HTMLElement.prototype)+"<br/>");
document.write("registerElement:" + ("registerElement" in document)+"<br/>");
document.write("import:" + ("import" in document.createElement("link"))+"<br/>");
检测结果:
谷歌chrome肯定是不用测了,自己主导的标准,支持没得说,移动端上,QQ和微信上都支持,腾讯的产品自己有定制内核,我的android手机(4.2版本)自带的浏览器不支持,我用hbuilder开发的app(webview)在手机上也不支持,但是通过web components项目封装的js(为了兼容低版本浏览器写的库,高版本chrome不用引入)可以支持自定义元素,影子节点和html引入的js有报错不支持。
web components折腾记