web components折腾记

Posted 欧米茄

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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折腾记的主要内容,如果未能解决你的问题,请参考以下文章

Atom编辑器折腾记

[转] webpack折腾记:性能优化

VS Code 折腾记 - (19) 一些相对实用的编码体验插件(偏前端)

Chrome调试折腾记_JS断点调试技巧

(汇总)Spring Boot 实践折腾记 & Spring Boot 2.x 实践记

Angular 2 + 折腾记 : 动手写一个不怎么靠谱的上传组件