Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[译] web组件简介 #9

Open
ustccjw opened this issue Sep 11, 2015 · 0 comments
Open

[译] web组件简介 #9

ustccjw opened this issue Sep 11, 2015 · 0 comments
Labels

Comments

@ustccjw
Copy link
Owner

ustccjw commented Sep 11, 2015

web组件简介

原文链接

Web组件(Web Components)

我们所说的web组件是一些在w3c约束下工作的标准集合,并且在浏览器中进行呈现。简而易之,它们允许我们在自定义的HTML元素中绑定标记和样式。更令人感到惊奇的是,它们对所有绑定的HTML和CSS实行完全封装。这意味着你写的样式总能够按照你的意图渲染(ps. 并且无副作用),无法通过外部的Javascript来窥探你的HTML。

如果你想玩下内置的Web组件,我建议你使用Chrome Canary,它能提供最好的支持。一定要在chrome://flags中确保下面的设置:

  • Experimental JavaScript
  • Experimental Web Platform Features
  • HTML Imports

一个实际的例子

想想你如何来实现一个图像幻灯片,可能实现起来如下:

HTML:

<div id="slider">
    <input checked="" type="radio" name="slider" id="slide1" selected="false">
    <input type="radio" name="slider" id="slide2" selected="false">
    <input type="radio" name="slider" id="slide3" selected="false">
    <input type="radio" name="slider" id="slide4" selected="false">
    <div id="slides">
        <div id="overflow">
            <div class="inner">
                <article>
                    <img src="./rock.jpg" alt="an interesting rock">
                </article>
                <article>
                    <img src="./grooves.jpg" alt="some neat grooves">
                </article>
                <article>
                    <img src="./arch.jpg" alt="a rock arch">
                </article>
                <article>
                    <img src="./sunset.jpg" alt="a dramatic sunset">
                </article>
            </div>
        </div>
    </div>
    <label for="slide1"></label>
    <label for="slide2"></label>
    <label for="slide3"></label>
    <label for="slide4"></label>
</div>

这是一块还不错的HTML,我们甚至还没有包含CSS。但是想象一下如果我们可以删除所有额外的源码,将代码减少到只包括重要的部分。那看起来怎么样?

<img-slider>
    <img src="./sunset.jpg" alt="a dramatic sunset">
    <img src="./arch.jpg" alt="a rock arch">
    <img src="./grooves.jpg" alt="some neat grooves">
    <img src="./rock.jpg" alt="an interesting rock">
</img-slider>

不是太寒酸嘛!我们已经抛弃了样板文件,我们留下来的代码只包含我们所关心的东西。这是Web组件将允许我们的做的事。但在我深入细节之前我想告诉你另一个故事。

在阴影中隐藏

多年来,浏览器开发者都用一个卑鄙的伎俩来隐藏他们的袖子。来看一下

<video src="./foo.webm" controls></video>

video

这里有一个播放按钮,一个播放进度条,一个播放时间,一个音量调节器。这些东西都不需要你写任何标记,只要你用<video>标记,这些部件都会出现

但实际上,你看到的是一个错觉,浏览器开发者需要一种方法来保证无论我们在页面上添加任何古怪的HTML,CSS或Javascript,这些标签总能够渲染的一致。为此,他们创造了一个秘密通道的方式,可以对我们隐藏他们的代码。他们称之为:the Shadow DOM.

如果你用chrome,可以打开你的开发者工具,选中Show Shadow DOM,就可以看到

video 详细

你会发现里面有大量的HTML隐藏代码。仔细看看,你会发现有播放按钮,播放进度条,播放时间,音量调节器等元素。

现在回想我们的幻灯片。假如我们能访问阴影DOM并且可以定义我们自己的标记类似video,那将会怎样?那么我们就可以真正意义上实现我们自定义的img-slider标记。

让我们来看看如何实现这一点,使用web组件的第一支柱:模板。

模板

每个优秀的构建项目必须先得有一个蓝图,对于web组件,蓝图来自于template标记。模板标记允许你存储一些标记在页面上以供你稍后克隆和重用。如果你之前用过类似的模板库如mustache或者handlebars,那么对template会感到亲切。

<template>
    <h1>Hello there!</h1>
    <p>This content is top secret :)</p>
</template>

模板里的一切都被浏览器认为是滞后的。这意味着模板里的外部资源标签如,,

因此,创建我们自己的的第一步是将所有的HTML和CSS放进中。

<template>
    <style>
        * {
            -webkit-box-sizing: border-box;
            -moz-box-sizing: border-box;
            -ms-box-sizing: border-box;
            box-sizing: border-box;
        }

        #slider {
            max-width: 600px;
            text-align: center;
            margin: 0 auto;
        }

        #overflow {
            width: 100%;
            overflow: hidden;
        }

        #slides .inner {
            width: 400%;
        }

        #slides .inner {
            -webkit-transform: translateZ(0);
            -moz-transform: translateZ(0);
            -o-transform: translateZ(0);
            -ms-transform: translateZ(0);
            transform: translateZ(0);

            -webkit-transition: all 800ms cubic-bezier(0.770, 0.000, 0.175, 1.000);
            -moz-transition: all 800ms cubic-bezier(0.770, 0.000, 0.175, 1.000);
            -o-transition: all 800ms cubic-bezier(0.770, 0.000, 0.175, 1.000);
            -ms-transition: all 800ms cubic-bezier(0.770, 0.000, 0.175, 1.000);
            transition: all 800ms cubic-bezier(0.770, 0.000, 0.175, 1.000);

            -webkit-transition-timing-function: cubic-bezier(0.770, 0.000, 0.175, 1.000);
            -moz-transition-timing-function: cubic-bezier(0.770, 0.000, 0.175, 1.000);
            -o-transition-timing-function: cubic-bezier(0.770, 0.000, 0.175, 1.000);
            -ms-transition-timing-function: cubic-bezier(0.770, 0.000, 0.175, 1.000);
            transition-timing-function: cubic-bezier(0.770, 0.000, 0.175, 1.000);
        }

        #slides article {
            width: 25%;
            float: left;
        }

        #slide1:checked ~ #slides .inner {
            margin-left: 0;
        }

        #slide2:checked ~ #slides .inner {
            margin-left: -100%;
        }

        #slide3:checked ~ #slides .inner {
            margin-left: -200%;
        }

        #slide4:checked ~ #slides .inner {
            margin-left: -300%;
        }

        input[type="radio"] {
            display: none;
        }

        label {
            background: #CCC;
            display: inline-block;
            cursor: pointer;
            width: 10px;
            height: 10px;
            border-radius: 5px;
        }

        #slide1:checked ~ label[for="slide1"],
        #slide2:checked ~ label[for="slide2"],
        #slide3:checked ~ label[for="slide3"],
        #slide4:checked ~ label[for="slide4"] {
            background: #333;
        }
    </style>
    <div id="slider">
        <input checked="" type="radio" name="slider" id="slide1" selected="false">
        <input type="radio" name="slider" id="slide2" selected="false">
        <input type="radio" name="slider" id="slide3" selected="false">
        <input type="radio" name="slider" id="slide4" selected="false">
        <div id="slides">
            <div id="overflow">
                <div class="inner">
                    <article>
                        <img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/5689/rock.jpg">
                    </article>

                    <article>
                        <img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/5689/grooves.jpg">
                    </article>

                    <article>
                        <img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/5689/arch.jpg">
                    </article>

                    <article>
                        <img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/5689/sunset.jpg">
                    </article>      
                </div> <!-- .inner -->
            </div> <!-- #overflow -->
        </div>
        <label for="slide1"></label>
        <label for="slide2"></label>
        <label for="slide3"></label>
        <label for="slide4"></label>
    </div>
</template>

接着,我们准备将template移进阴影DOM里。

Shadow DOM

阴影DOM给我们提供了iframes的最好特性,样式和标记的封装。

为了创建阴影DOM,选择一个元素然后调用其createShadowRoot方法。这将会返回一个文档碎片,你可以往里面填充内容。

<div class="container"></div>

<script>
    var host = document.querySelector('.container');
    var root = host.createShadowRoot();
    root.innerHTML = '<p>How <em>you</em> doin?</p>'
</script>

Shadow Host

在阴影DOM的术语里,你调用creatreShadowRoot的元素被称之为Shadow Host。这是唯一用户可见的片段,而且在这里要求用户提供组件相关的内容。

如果你之前考虑过

Shadow Root

createShadow返回的文档碎片称之为Shadow Root。Shadow Root及其子孙就是对用户不可见的,但是当浏览器检测到我们的自定义标记时就会实际渲染Shadow Root及其子孙。

Shadow Boundary

Shadow Root里的任何的HTML和CSS都被父文档的称之为Shadow Boundary的不可见障碍所保护。Shadow Boundary阻止父文档中CSS的渗入到Shadow DOM中,它也阻止外部的Javascript遍历Shadow Root。

也就是说假如你在shadow DOM中指定了所有h3的color: red。同时,在父文档中,你指定h3的color: blue。在这种情况下,在shadow DOM中的h3将会是红色,shadow DOM外面的h3将会是绿色。得益于Shadow Boundary,这两个样式完美地相互忽略。

相应的,当父文档用$('h3')来寻找h3元素,Shadow Boundary将会阻止任何对shadow root的探测,这个选择器将只返回shadow DOM外面的h3.

Shadowy Sliders

为了得到img-slider的Shadow DOM,我们需要创建Shadow host,并且用模板的内容来填充它。

<template>
    <!-- Full of slider awesomeness -->
</template>

<div class="img-slider"></div>

<script>
    // Add the template to the Shadow DOM
    var tmpl = document.querySelector('template');
    var host = document.querySelector('.img-slider');
    var root = host.createShadowRoot();
    root.appendChild(document.importNode(tmpl.content, true));
</script>

在这个例子中,我们创建了一个div,并且添加类名为img-silder,因此它可以用来充当shadow host。

我们选择模板,用document.importNode来对其进行深拷贝。然后添加到我们创建的shadow root内部('codepen效果')。

Insertion Points

我们的img-slider在shadow DOM内部,但是图片的路径是硬编码的。就像

为了将这些内容插入shadow DOM中,我们用新的标签。标签用CSS选择器来从shadow host中选择元素,将它们在shadow DOM中构建。这些构建的元素称之为Insertion Points

我们将简化问题,假设幻灯片只有4张图片,这样我们可以用nth-of-type来创建4个insertion points。

<template>
    ...
    <div class="inner">
        <article>
            <content select="img:nth-of-type(1)"></content>
        </article>
        <article>
            <content select="img:nth-of-type(2)"></content>
        </article>
        <article>
            <content select="img:nth-of-type(3)"></content>
        </article>
        <article>
            <content select="img:nth-of-type(4)"></content>
        </article>
    </div>
</template>

现在我们能够填充img-slider。

<div class="img-slider">
    <img src="./rock.jpg" alt="an interesting rock">
    <img src="./grooves.jpg" alt="some neat grooves">
    <img src="./arch.jpg" alt="a rock arch">
    <img src="./sunset.jpg" alt="a dramatic sunset">
</div>

这看起来很酷,我们还将更进一步,使用自定义标记img-slider。

自定义元素

创建你自己的HTML元素听说起来可能很吓人但实际上非常简单。在web组件上说,这样的新元素是一个自定义元素,仅有的两个条件是它的名字必须包含破折号,以及它的原型必须继承HTMLElement。

<template>
    <!-- Full of image slider awesomeness -->
</template>

<script>
    // Grab our template full of slider markup and styles
    var tmpl = document.querySelector('template');

    // Create a prototype for a new element that extends HTMLElement
    var ImgSliderProto = Object.create(HTMLElement.prototype);

    // Setup our Shadow DOM and clone the template
    ImgSliderProto.createdCallback = function() {
        var root = this.createShadowRoot();
        root.appendChild(document.importNode(tmpl.content, true));
    };

    // Register our new element
    var ImgSlider = document.registerElement('img-slider', {
        prototype: ImgSliderProto
    });
</script>

Object.create方法返回一个继承HTMLElement的原型对象。当解释器发现文档中的自定义标记时,它将会检查看是否其有一个createdCallback的方法。如果发现了这个方法它将会立即运行该方法。这是一个开始工作的好地方,因此我们创建Shadow DOM并且将我们的模板克隆并插进去。

我们用方法registerElement来注册自定义元素,第一个参数是标记的名字,第二个参数是元素的原型。

现在我们的元素已经被注册,我们可以有多种方法来使用它。一种是直接在HTML中用标记。另外一种是通过Javascript来调用document.createElement("img-slider")或者用document.registerElement返回的构造函数对象ImgSlider。

支持

目前浏览器对web组件的支持参差不齐,尽管一直在提高。下图是目前的支持情况:

浏览器对web组件的支持情况

但是不要丧失使用它们的勇气!Mozilla 和 Google Chrome一直在努力构建的polyfill库在所有现代浏览器中支持web组件。这意味着你现在就可以开始使用这些技术并且提供反馈。

@ustccjw ustccjw added the blog label Sep 11, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant