【HTML】Web Component

  近年来,web 开发者们通过插件或者模块的形式在网上分享自己的代码,便于其他开发者们复用这些优秀的代码。同样的故事不断发生,人们不断的复用 JavaScript 文件,然后是 CSS 文件,当然还有 HTML 片段。但是你又必须祈祷这些引入的代码不会摧毁你的网站或者web app(有个根本问题,导致 HTML 和 JavaScript 构建出来的部件难以使用:部件中的 DOM 树并没有封装起来。 封装的缺乏意味着文档中的样式表会无意中影响部件中的某些部分; JavaScript 可能在无意中修改部件中的某些部分;你书写的 ID 也可能会把部件内部的 ID 覆盖)。

  Web Components 是这类问题最好的良药,通过一种标准化的非侵入的方式封装一个组件,每个组件能组织好它自身的 HTML 结构、CSS 样式、JavaScript 代码,并且不会干扰页面上的其他代码。


Web Component 由四部分组成:

  • HTML Imports
  • HTML Templates
  • Custom Elements
  • Shadow DOM

我先解释一下:Template可以让你用声明的方式定义你的自定义元素的内容。Shadow DOM可以让一个元素的style、ID、class只作用到其本身。自定义元素可以让你自定义HTML标签。通过把这些跟HTML导入结合起来,你自定义的web 组件会变得模块化,具有复用性。任何人添加一个Link标签就可以使用它。


一、HTML Imports

在 HTML 主文件中添加要导入的文件

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

在HTML导入文件中不需要doctype、html、head、body这些标签

  浏览器解析HTML文档的方式是线性的,这就是说HTML顶部的script会比底部先执行。并且,浏览器通常会等到JavaScript代码执行完毕后,才会接着解析后面的代码。

  为了不让script 妨碍HTML的渲染,你可以在标签中添加async或defer属性(或者你也可以将script 标签放到页面的底部)。defer 属性会延迟脚本的执行,直到全部页面解析完毕。async 属性让浏览器异步地执行脚本,从而不会妨碍HTML的渲染。那么,HTML 导入是怎样工作的呢?

  HTML导入文件中的脚本就跟含有defer属性一样。例如在下面的示例中,index.html会先执行script1.js和script2.js ,然后再执行script3.js。

index.html

<link rel="import" href="component.html"> // 1.   
<title>Import Example</title>  
<script src="script3.js"></script>        // 4.  

component.html

<script src="js/script1.js"></script>     // 2.   
<script src="js/script2.js"></script>     // 3.  

二、HTML Templates

原来的模板形式:

  • script 元素
<script type="text/template">
    <div>
        this is your template content.
    </div>
</script>
  • textarea 元素
<textarea style="display:none;">
    <div>
        this is your template content.
    </div>
</textarea>

现在的模板形式:

  • template 元素
<template>
    <div>
        this is your template content.
    </div>
</template>

主要有四个特性:

  1. 惰性:在使用前不会被渲染;
  2. 无副作用:在使用前,模板内部的各种脚本不会运行、图像不会加载等;
  3. 内容不可见:模板的内容不存在于文档中,使用选择器无法获取;
  4. 可被放置于任意位置:即使是 HTML 解析器不允许出现的位置,例如作为 <select> 的子元素。

三、Custom Elements

注册新元素

使用 document.registerElement() 可以创建一个自定义元素:

var XFoo = document.registerElement('x-foo');
document.body.appendChild(new XFoo());

document.registerElement() 的第一个参数是元素的标签名。这个标签名必须包括一个连字符(-)。
第二个参数是一个(可选的)对象,用于描述该元素的 prototype。
自定义元素默认继承自 HTMLElement,因此上一个示例等同于:

var XFoo = document.registerElement('x-foo', {
  prototype: Object.create(HTMLElement.prototype)
});

生命周期及回调

  • createdCallback
  • attachedCallback
  • detachedCallback
  • attributeChangedCallback

扩展原生元素

假设平淡无奇的原生 <button> 元素不能满足你的需求,你想将其增强为一个“超级按钮”,可以通过创建一个继承 HTMLButtonElement.prototype 的新元素,来扩展 <button> 元素:

var MegaButton = document.registerElement('mega-button', {
  prototype: Object.create(HTMLButtonElement.prototype)
});

元素提升

你有没有想过为什么 HTML 解析器对非标准标签不报错?比如,我们在页面中声明一个 <randomtag>,一切都很和谐。根据 HTML 规范的表述:

非规范定义的元素必须使用 HTMLUnknownElement 接口。 —— HTML 规范

是非标准的,它会继承 HTMLUnknownElement。

对自定义元素来说,情况就不一样了。拥有合法元素名的自定义元素将继承 HTMLElement。

// “tabs”不是一个合法的自定义元素名
document.createElement('tabs').__proto__ === HTMLUnknownElement.prototype

// “x-tabs”是一个合法的自定义元素名
document.createElement('x-tabs').__proto__ == HTMLElement.prototype

Unresolved 元素

  由于自定义元素是通过脚本执行 document.registerElement() 注册的,因此 它们可能在元素定义被注册到浏览器之前就已经声明或创建过了。例如:你可以先在页面中声明 ,以后再调用 document.registerElement(‘x-tabs’)。

  在被提升到其定义之前,这些元素被称为 unresolved 元素。它们是拥有合法自定义元素名的 HTML 元素,只是还没有注册成为自定义元素。


四、Shadow DOM

  有了 Shadow DOM,元素就可以和一个新类型的节点关联。这个新类型的节点称为 shadow root。与一个 shadow root 关联的元素称作一个 shadow host。shadow host 的内容不会渲染;shadow root 的内容会渲染。

关键点:shadow host 的内容投射(projected)到 <content> 元素出现的地方

示例代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div class="widget">Shadow Dom</div>

    <template>
        <h1>Hello, I am <content></content></h1>
    </template>
    <script>
        var host = document.querySelector('.widget');
        var root = host.createShadowRoot();
        var template = document.querySelector('template');

        root.appendChild(document.importNode(template.content, true));
    </script>
</body>
</html>

示例效果图:
这里写图片描述

扩展:高级投射

在上面的例子中, 元素挑选了 shadow host 的所有内容。通过使用 select 特性,你可以控制 content 元素投射的内容。你也可以使用多个 content 元素。

比如说,如果你有一个包含如下内容的文档:

<div id="nameTag">
    <div class="first">Bob</div>
    <div>B. Love</div>
    <div class="email">bob@</div>
</div>

shadow root 使用 CSS 选择器来选择特定内容:

<div style="background: purple; padding: 1em;">
    <div style="color: red;">
        <content select=".first"></content>
    </div>
    <div style="color: yellow;">
        <content select="div"></content>
    </div>
    <div style="color: blue;">
        <content select=".email"></content>
    </div>
</div>

注意: select 只能选择 host 节点的直接子元素。也就是说,你不能选择后代元素(例如 select=”table tr”)。

Shadow DOM 样式封装

1、宿主元素(:host)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <style>
        p {
            color: red;
            font-size: 18px;
        }
    </style>
    <p class="normal">我是一个普通文本</p>
    <p class="shadow"></p>
    <script>
        var host = document.querySelector('.shadow');
        var root = host.createShadowRoot();
        root.innerHTML = `
        <style>
            :host(p.shadow) { 
            color: blue; 
            font-size: 24px; 
            } 
        </style>
        我是一个影子文本`;
    </script>
</body>
</html>

这里写图片描述
注意上例中 shadow DOM 内的选择器是 :host(p.shadow),而不是跟外部平级的 :host(p)。 因为:host(p) 的优先级低于外部的 p 选择器,所以不会生效。需要使用 :host(p.shadow) 提升优先级,才能将 .shadow 中的样式覆盖。

在一个 shadow root 内支持多种宿主类型

:host 还有一种使用场景,那就是你创建了一个主题库,想在相同的 Shadow DOM 内为不同类型的宿主元素提供样式化。

:host(x-foo:host) {
  /* 当宿主是 <x-foo> 元素时生效。 */
}

:host(x-bar:host) {
  /* 当宿主是 <x-bar> 元素时生效。 */
}

:host(div) {  {
  /* 当宿主或宿主的祖先元素是 <div> 元素时生效。 */
}

2、宿主祖先元素(:host-context)

有时需要通过外部条件来改变宿主元素的样式,例如,可能需要根据元素设置的CSS主题类,然后再其基础做对我们组件进行修改。

用法和 :host() 类似,使用:host-context()伪类,括号里面可以指定根选择器。比如下面示例是针对带theme-lightde类下所有h2元素有效。

:host-context(.theme-light) h2 {
  background-color: #eef;
}

试写第一个 WebComponent

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <link rel="import" href="components/favorite-colour.html">
</head>
<body>
    <favorite-colour></favorite-colour>
</body>
</html>

favorite-colour.html

<template>
    <style>
        .coloured {
            color: red;
        }
    </style>
    <p>My favorite colour is: <strong class="coloured">Red</strong></p>
</template>
<script>
    (function() {
        // Creates an object based in the HTML Element prototype
        var element = Object.create(HTMLElement.prototype);
        // Gets content from <template>
        var template = document.currentScript.ownerDocument.querySelector('template').content;
        // Fires when an instance of the element is created
        element.createdCallback = function() {
            // Creates the shadow root
            var shadowRoot = this.createShadowRoot();
            // Adds a template clone into shadow root
            var clone = document.importNode(template, true);
            shadowRoot.appendChild(clone);
        };
        document.registerElement('favorite-colour', {
            prototype: element
        });
    }());
</script>

猜你喜欢

转载自blog.csdn.net/u013451157/article/details/80558202
今日推荐