Web Component

Author Avatar
Aryb1n 5月 26, 2017

好累啊..腿好酸

看了一本书叫Web Component实战

准备

Web Component有几个模块:
模板元素,Shadow DOM,自定义元素,HTML Import

模板元素

interface HTMLTemplateElement: HTMLELement {
    readonly arttribute DocumentFragment content;
}

HTML Template元素的唯一一个元素content(类型为DocumentFragment)

检测是不是支持HTML Template

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8">
        <title>Web Component: Template support</title>
    </head>
    <body>
        <h1 id="message"></h1>
        <script>
            var isTemplateSupported = function() {
                var template = document.createElement("template");
                return 'content' in template;
            }
            var isSupported = isTemplateSupported(),
                message = document.getElementById('message');
            if(isSupported) {
                message.innerHTML = "Supported";
            } else {
                message.innerHTML = "Not Supported";
            }
        </script>
    </body>
</html>

模板是延迟加载的,要手动激活
方法有两种

cloneNode

<Node> <Target node>.cloneNode(<Boolean paramenter>)

参数:

- true 深克隆 目标节点的子节点也被clone
- false 浅克隆

返回值:

- <Node>
<div id="container"> </div>
<template id="aTemplate">
    <h1>
        Template is activated using cloneNode.
    </h1>
</template>
<script>
    var aTemplate = document.querySelector('#aTemplate'),
    container = document.getElementById("container"),
    templateContent = aTemplate.content,
    activeContent = templateContent.cloneNode(true); //key

    container.appendChild(activeContent);
</script>

importNode

<Node> document.importNode(<target Node>, <Boolean parameter>)

这个更像是函数式的写法

<div id="container"> </div>
<template id="aTemplate">
    <h1>
        Template is activated using cloneNode.
    </h1>
</template>
<script>
    var aTemplate = document.querySelector('#aTemplate'),
    container = document.getElementById("container"),
    templateContent = aTemplate.content,
    activeContent = document.importNode(templateContent, true); //key

    container.appendChild(activeContent);
</script>

HTML Import

可以像iframe一样,把外部的HTML嵌入页面

<link rel="import" href="fileName.html">

检测是否支持

var isImportSupport = function() {
    var link = document.createElement('link');
    return 'import' in link;
}

访问被引入的文档(通过import属性)
=> message.html

<h1>This is from another HTML file document.</h1>

=> index.html

<head>
    <link rel="import" href="message.html">
</head>
<body>
<script>
    (function() {
        var externalDocument = 
            document.querySelector('link[rel="import"]').import;
        var headerElement = externalDocument.querySelector('h1');
//        document.body.appendChild(headerElement);
        document.body.appendChild(headerElement.cloneNode(true));
    })();
</script>
</body>

原文这里也是用了cloneNode
即为

document.body.appendChild(headerElement.cloneNode(true));

但试验了一下好像直接

document.body.appendChild(headerElement);

也是可以的

Shadow DOM

为了防止防止外部文档影响到Web Component,比如

  • 文档样式表
  • 文档的javascript代码
  • 出现重复id

Shadow DOM 相当于把web Component封装起来,不受外部作用域影响

检测是否

var isShadowDOMSupported = function() {
    return 'createShadowRoot' in document.body
}
Shadow tree

可以把一个Shadow tree 挂到某个DOM元素上,这个元素就作为宿主元素

<body>
    <div id="aShadowHost"></div> <!-- 挂载点 -->
    <template id="selectorTemplate"> <!-- 模板 可以clone,然后丢到shadow DOM 里-->
        <style>
            :host input{
                background: lightyellow;
            }
            :host .labelClass{
                color: blue;
            }
        </style>
        <form>
            ... 省略若干html
        </form>
    </template>
    <script>
        (function() {
            var aShadowHost = document.getElementById('aShadowHost');
            var shadowRoot = aShadowHost.createShadowRoot(); //重点,敲黑板,相当于给aShadowHost挂载了一颗Shadow Tree
            var TemplateContent = ... ; //此处省略把`selectorTemplate`cloneNode出来的过程
            shadowRoot.appendChild(TemplateContent); //成功把模板挂到shadowRoot下面
            // 其实可以挂很多个shadow tree
            //现在的状况
            /* div#aShadowHost
             *  /=> shadow-root
             *      /=> style
             *      /=> form
             *  /=> shadow-root 如果多createShadowRoot几次就能得到多个shadow DOM子树
             *      /=> ....
             *  /=> ....
             */
        })();
    </script>
</body>

自定义元素

检测

var isCustomElementSupportws = function() {
    return 'registerElement' in document;
}

大概过程,具体参考MDN

var newObj = Object.create(HTMLElement.prototype);
Object.defineProperties(newObj, {
    title: {
        writeable:true
    },
    country: {
        writeable:true,
        value: 'India'
    }

});
//定义生命周期方法(这个是不是就叫做钩子)
newObj.createCallback = function() {
   //Do-something 
}
//注册元素
var newTag = document.registerElement('Tag-name', {
    prototype: newObj   // 把我们前面辛辛苦苦create出来的obj注册成功了
});

使用

<Tag-name>
</Tag-name>

生命周期: created => attached => detached => attributeChanged

节点分布

两种插入点

content

对Shadow Host的子节点占位,反向映射Shadow Host的子节点

这个不太好理解,看他这个例子


shadow

shadow插入点是Shadow Tree的占位符,反向映射了Shadow Tree中的元素

哇 这个也不太好理解


然后发现了一个完整的Demo

抄完这个Demo就去写作业

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8">
        <title>Demo</title>
        <style>
            header-element:unresolved{
                visibility:hidden;
            }
            header-element:unresolved:after{
                content: 'Registering Element...';
                color: red;
                visibility:visible;
            }
        </style>
    </head>
    <body>
        <template id="headerTemplate">
            <style>
                :host{
                    text-transform:lowercase;
                }
                :host::shadow h1{
                    color:orange;
                }
                :host::content b{
                    color:blue;
                }
            </style>
            <h1> Hello <content></content> </h1>
        </template>
        <script>
            (function() {
                var objectPrototype = Object.create(HTMLElement.prototype);
                objectPrototype.createdCallback = function() {
                    var shadow = this.createShadowRoot(),
                        templateContent = document.querySelector('#headerTemplate').content;
                    var templateNodes = document.importNode(templateContent, true);
                    shadow.appendChild(templateNodes); // 成功挂载,要累死我了
                };
                window.setTimeout(function() {
                    document.registerElement('header-element', {
                        prototype: objectPrototype
                    }) //延时注册
                }, 3000);
            })();
        </script>
        <header-element>
            <b>Web Component</b>
        </header-element>
    </body>
</html>

心都碎了,说好的

:host::shadow h1{
    color:orange;
}
:host::content b{
    color:blue;
}

这样子.应该是Hello橙色,web Component蓝色啊
实际上运行结果是
firefox跑不出来
chrome上有加载效果,但加载出来都是橙色的,并且警告我

::shadow pseudo-element is deprecated. See https://www.chromestatus.com/features/6750456638341120 for more details.

后来

书的后几章讲了一些具体的实现组件化web的库
Polymer JS, Mozilla Brick, Bosonic, ReactJS

喔,,以后学一下ReactJS,稍微看了下,总感觉像是在拼接字符串

参考

https://github.com/w3c/webcomponents
http://w3c.github.io/webcomponents/spec/shadow/
https://www.toobug.net/article/what_is_shadow_dom.html
https://developer.mozilla.org/zh-cn/docs/web/web_components