今天 ,Web 组件已经从本质上改变了HTML。初次接触时,它看起来像一个全新的技术。Web组件最初的目的是使开发人员拥有扩展浏览器标签的能力,能够自由的进行定制组件。面对新的技术,你可能会以为无从下手。那这篇文章将为你揭开Web组件神秘的面纱。若是你已经熟知HTML标签和DOM编程,已经拥有了大量可用的Web组件,那么你已是Web组件专家了。javascript
随着各式各样的用户需求,浏览器的原生组件已经没法知足需求。Web组件也就变得愈来愈重要。css
咱们将以自定义一个传统三方插件为例来介绍Web组件。html
首先,须要引用插件的CSS和JavaScript资源:java
<link rel="stylesheet" type="text/css" href="my-widget.css" /> <script src="my-widget.js"></script>
接下来,咱们须要向页面中添加占位符。web
<div data-my-widget></div>
最后,咱们须要使用脚原本找到而且实例化这个占位符为Web组件。sql
// 使用 jQuery 初始化组件 $(function() { $('[data-my-widget]').myWidget(); });
经过以上是三个基本步骤。已经完成了在页面中添加了自定义插件,可是浏览器没法肯定自定义组件的生命周期,若是经过如下方式声明则使自定义组件生命周期变得清晰了。chrome
el.innerHTML = '<div data-my-widget></div>';
由于这不是一个内置的组件,咱们如今必须手动实例化新组件,编程
$(el).find('[data-my-widget]').myWidget();
避免这种复杂设置方法的有效方式是彻底抽象DOM交互。不过,这个动做也比较复杂,须要建立框架或者库来自定义组件。浏览器
组件一旦被声明,占位符已经被替代为原生的HTML标记:ruby
<div data-my-widget> <div class="my-widget-foobar"> <input type="text" class="my-widget-text" /> <button class="my-widget-button">Go</button> </div> </div>
这样作的弊端是,自定义组件的标记和普通HTML组件的标记混杂在一块儿,没有清晰的分割和封装。这就不可避免的会出现命名及样式等冲突。
随着三方Web组件的发展,它已经成为了Web开发不可或缺的部分:
<!—导入: --> <link rel="import" href="my-widget.html" /> <!—使用:--> <my-widget />
在这个实例中,咱们经过导入HTML来添加组件而且当即使用。
更重要的是,由于<my-widget />是浏览器原生支持的组件,它直接挂在浏览器的生命周期中,容许咱们像添加原生组件同样添加三方组件。
el.innerHTML = '<my-widget />'; // 插件当前已经被实例化
当查看这个组件的HTML 源码,你会发现它仅仅是一个单一的标签。若是启用浏览器Shadow DOM 特性,才能够查看标签内的组件,你将会发现一些有趣的事情,
当咱们谈论Web组件时,咱们不是在谈论一门新技术。Web组件最初的目的是给咱们封装能力,它能够经过自定义组件和Shadow DOM 技术来实现。因此,接下来,咱们将着重介绍下这两项技术。介绍以上两个技术以前,咱们最好先梳理下已知浏览器原生组件。
咱们知道组件能够经过HTML标记或JavaScript来实例化:
使用 标记实例化:
<input type="text" /> document.createElement('input'); el.innerHTML = '<input type="text" />';
使用JaveScript实例化:
document.createElement('input')
document.createElement('div')
添加带有属性的HTML标签:
// 建立带有属性的input标签... el.innerHTML = '<input type="text" value="foobar" />'; //这时value属性已经同步 el.querySelector('input').value;
组件能够响应属性的变化:
// 若是咱们更改value 属性值 input.setAttribute('value', 'Foobar'); //属性值会当即更改 input.value === 'Foobar'; // true
组件能够有内部隐藏的DOM结构:
<!—使用一个input实现复杂的日历功能--> <input type="date" /> // 尽管其内部结构比较复杂,可是已经封装成为一个组件 dateInput.children.length === 0; // true
组件可使用子组件:
<!—能够给组件提供任意个 'option' 标签--> <select> <option>1</option> <option>2</option> <option>3</option> </select>
组件能够为其子组件提供样式:
dialog::backdrop { background: rgba(0, 0, 0, 0.5); }
最后,组件能够有内置样式。和自定义插件不一样,咱们不须要为浏览器的原生控件引用CSS文件。
有了以上的了解,咱们已经具有了解Web组件的基础。使用自定义组件和Shadow DOM,咱们能够在咱们的插件中定义全部这些标准行为。
注册一个新组件也比较简单:
var MyElement = document.register('my-element'); // 'document.register' 返回一个构造函器
你也许注意到上面的自定义组件名称包含一个链接符。这是为了确保自定义组件名称不和浏览器内置组件不冲突。
如今<my-element />这个组件具有了原生组件的特性,
因此,自定义组件也一样能够进行普通的DOM操做:
document.create('my-element'); el.innerHTML = '<my-element />'; document.create('my-element');
当前,这个自定义组件仅仅有框架,而没有内容,下面让咱们向其中添加一些内容:
//咱们将提供'document.register'的第二个参数: document.register('my-element', { prototype: Object.create(HTMLElement.prototype, { createdCallback: { value: function() { this.innerHTML = '<h1>ELEMENT CREATED!</h1>'; } } }) });
在这个例子中,咱们设置自定义组件的prototype,使用Object.create 方法建立一个继承于HTMLElement的对象。在这个方法中修改该组件的属性 innerHTML。
咱们定义了createdCallback方法,在每次声明实例时调用。你一样能够有选择性的定义attributeChangedCallback、 enteredViewCallback 和leftViewCallback等方法。
目前为止咱们实现了动态修改自定义组件内容的功能,咱们仍然须要提供自定义组件的封装方法,用于隐藏其内部组件。
咱们须要完善下createdCallback方法。本次,除了修改innerHTML以外,咱们添加一些额外的操做:
createdCallback: { value: function() {var shadow = this.createShadowRoot(); shadow.innerHTML = '<h1>SHADOW DOM!</h1>'; } }
在这个例子中, 你会注意到‘SHADOW DOM!’,可是查看源码时你会发现只有空白的<my-element /> 标签而已。这里使用建立Shadow Root 方法替代了直接修改页面。
Shadow Root中的任何组件,是肉眼可见的,可是和当前页面的样式和DOM API相隔离。这样就实现了自定义组件是一个独立组件的假象。
目前为止,咱们的自定义组件是空标签,可是若是向其中添加内部组件会出现什么现象呢?
咱们假设自定义组件包含的节点以下,
<my-element> 这是一个轻量级 DOM。 <i>hello</i> <i>world</i> </my-element>
一旦针对于这个组件的 Shadow Root 被建立,它的子节点再也不存在。咱们这些隐藏的子节点封装为轻量级DOM节点。
若是禁用了 Shadow DOM,上面这个例子仅仅会显示为:这是一个轻量级 DOM‘ hello world ’。
当咱们在createdCallback方法中设置 Shadow DOM后,咱们可使用新增内容分配轻量级DOM组件到Shadow DOM 中。
createdCallback: { value: function() {var shadow = this.createShadowRoot(); // 子组件'i' 标签如今已经消失了 shadow.innerHTML = ‘轻量级 DOM 中的 "i" 标签为: ' + '<content select="i" />'; //如今,在 Shadow DOM 中只有 'i' 标签是能够见的。 } }
Shadow DOM 最重要的做用是建立了和当前页面隔离的Web组件,使Web组件不受当前页面样式和JaveScript脚本的影响。
createdCallback: { value: function() {var shadow = this.createShadowRoot(); shadow.innerHTML = "<style>span { color: green }</style>" + "<span>I'm green</span>"; } }
反之,在 Shadow DOM 中定义的样式也不会影响以外的标签样式。
<my-element /> <span>I'm not green</span>
当隐藏自定义组件内部标记,有时也须要在当前页面对组件中的内部特定组件进行样式设置。
例如,若是咱们自定义一个日历插件,在不容许用户控制整个插件的状况下,容许最终用户去定义按钮的样式。
这是其中的部分特性和伪组件:
createdCallback: { value: function() {var shadow = this.createShadowRoot(); shadow.innerHTML = 'Hello <em part="world">World</em>'; } }
这是在当前页面设置自定义组件内部组件样式的方法:
my-element::part(world) { color: green; }
这部份内容介绍了封装web组件的基本方式。Shadow DOM 是咱们能够任意修改Web组件中的标签。在例子中,咱们设置了“World”的样式,可是使用者却没法判断它是<em>标签。
在你尝试自定义Web组件以前,须要确保浏览器的相关特性已经打开。若是使用 Chrome,在 Chrome 中打开chrome://flags ,而且开启“experimental Web Platform features”。
全部本文中介绍的内容,都是模拟一些简单的浏览器标准行为。咱们已经习惯于和原生的浏览器组件进行交互,所以自定义组件的步骤并非想象中的那个难。Web组件最终提供咱们一种实现简单、一致、可复用、封装和组合部件的方法,这是一个有意义的开始。