Web组件由四部分组成
- Template
- Shadow DOM (Chrome Opera支持)
- Custom Elements
- Packaging
Shadow DOM 组成
Shadow DOM
可以和一个根节点Shadow root
关联, 该Shadow DOM
元素称为Shadow Host
内容不会被渲染, 而Shadow root
内容会被渲染。
但是,内容不应该放进Shadow DOM
内, 以便被搜索引擎
阅读器
等访问到, 可重用部件无意义的标记应该放进Shadow DOM
中
Shadow DOM从展现中分离细节
内容在文档内;展现在 Shadow DOM
里。 当需要更新的时候,浏览器会自动保持它们的同步。
Hi! My name is
通过select
特性, 可以使用多个元素并控制投射元素
Bob
B. Love
bob@
Shadow DOM 样式
Shadow DOM
定义的CSS
样式只在Shadow Root
下生效, 样式被封装起来
样式化宿主元素(host element)
:host
样式化Shadow DOM
元素, 并且无法影响到Shadow DOM
外的元素
:host(x-bar:host) {
/* 当宿主是 元素时生效。 */
}
:host(.different:host) {
/* 当宿主的类 时生效。 */
}
:host:hover {
/* 当鼠标放置到宿主上时生效。 */
opacity: 1;
}
^(Hat) 和 ^^(Cat)选择器
^
连接符等价于后代选择器(例如 div p {...}),只不过它能跨越 一个 shadow 边界。
^^
后代选择器能够跨越 任意数量的 shadow 边界。
querySelector()
支持该选择器
可以通过 shadowdom 样式化原生HTML控件
video ^ input[type="range"] {
background: hotpink;
}
插入点重置样式
var root = document.querySelector('div').createShadowRoot();
root.resetStyleInheritance = false;
{
reset-style-inheritance: true;
}
在插入点, 选择是否继承上级样式(只影响可继承的样式)
::content 伪元素 穿过插入点来指定样式
Light DOM
I'm not underlined
I'm underlined in Shadow DOM!
对于一个 ShadowRoot 或
插入点:reset-style-inheritance 意味着可继承的 CSS 属性在宿主元素处被设置为 initial,此时这些属性还没有对 shadow 中的内容生效。该位置称为上边界(upper boundary)。
对于
插入点:reset-style-inheritance 意味着在宿主的子元素分发到插入点之前,将可继承的 CSS 属性设置为 initial。该位置称为下边界(lower boundary)。
使用多个shadowdom
最近添加的树称为 younger tree。之前添加的树称为 older tree。
添加进宿主元素中的 shadow 树按照它们的添加顺序而堆叠起来,从最先加入的 shadow 树开始。最终渲染的是最后加入的 shadow 树。
如果一个 shadow 树中存在多个
插入点,那么仅第一个被确认,其余的被忽略。
"Shadow 插入点" (
) 作为占位符可以插入 ShadowDOM
普通插入点 (
) 作为占位符可以插入 普通DOM元素
如果一个元素托管着 Shadow DOM,你可以使用 .shadowRoot
来访问它的 youngest shadow root
如果不想别人乱动你的 shadow,那就将 .shadowRoot 重定义为 null:
Object.defineProperty(host, 'shadowRoot', {
get: function() { return null; },
set: function(value) { }
});
JS中构建 shadowdom
可以使用 HTMLContentElement 和 HTMLShadowElement 接口。
使用插入点从宿主元素中选择并"分发"到 shadow 树
无法遍历
中的 DOM。
.getDistributedNodes()
允许我们查询一个插入点的分布式节点:
Eric
Bidelman
Digital Jedi
footer text
可以在分布式节点上调用它的 .getDestinationInsertionPoints()
来查看它被分发进了哪个插入点中
Light DOM
Shadow DOM 可视化渲染工具:
Shadow DOM Visualizer
shadowdom 事件模型
事件会被重定向,使它看起来是从宿主元素上发出,而并非是 Shadow DOM 的内部元素。(event.path
来查看调整后的事件路径。)
以下事件永远无法越过 shadow 边界:
- abort
- error
- select
- change
- load
- reset
- resize
- scroll
- selectstart
自定义元素
使用场景
- 定义新的 HTML/DOM 元素
- 基于其他元素创建扩展元素
- 给一个标签绑定一组自定义功能
- 扩展已有 DOM 元素的 API
注册新元素
document.registerElement()
可以创建一个自定义元素
- 第一个参数是元素的标签名。这个标签名必须包括一个连字符(-)。
- 第二个参数是一个(可选的)对象,用于描述该元素的 prototype。在这里可以为元素添加自定义功能(例如:公开属性和方法)。
var XFoo = document.registerElement('x-foo', {
prototype: Object.create(HTMLElement.prototype)
});
// 非全局创建新元素, 可以放置到自己的命名空间内
var myapp = {};
myapp.XFoo = document.registerElement('x-foo');
// 扩展原生元素 要创建扩展自元素 B 的元素 A,元素 A 必须继承元素 B 的 prototype。
var MegaButton = document.registerElement('mega-button', {
prototype: Object.create(HTMLButtonElement.prototype)
});
// 以下方法为重载版本
var megaButton = document.createElement('button', 'mega-button');
//
添加JS属性和方法
var XFooProto = Object.create(HTMLElement.prototype);
// 1. 为 x-foo 创建 foo() 方法
XFooProto.foo = function() {
alert('foo() called');
};
// 2. 定义一个只读的“bar”属性
Object.defineProperty(XFooProto, "bar", {value: 5});
// 3. 注册 x-foo 的定义
var XFoo = document.registerElement('x-foo', {prototype: XFooProto});
// 4. 创建一个 x-foo 实例
var xfoo = document.createElement('x-foo');
// 5. 插入页面
document.body.appendChild(xfoo);
/* 更简洁的方式 */
var XFoo = document.registerElement('x-foo', {
prototype: Object.create(HTMLElement.prototype, {
bar: {
get: function() { return 5; }
},
foo: {
value: function() {
alert('foo() called');
}
}
})
});
生命周期回调方法
回调名称 | 调用时间点 |
---|---|
createdCallback | 创建元素实例 |
attachedCallback | 向文档插入实例 |
detachedCallback | 从文档中移除实例 |
attributeChangedCallback(attrName, oldVal, newVal) | 添加,移除,或修改一个属性 |
var proto = Object.create(HTMLElement.prototype);
proto.createdCallback = function() {
this.addEventListener('click', function(e) {
alert('Thanks!');
});
this.innerHTML = "I'm an x-foo!";
};
proto.attachedCallback = function() {...};
var XFoo = document.registerElement('x-foo', {prototype: proto});
用 Shadow DOM 封装内部实现
- 一种隐藏内部实现的方法,从而将用户与血淋淋的实现细节隔离开。
- 简单有效的样式隔离。
从 Shadow DOM 创建元素,跟创建一个渲染基础标记的元素非常类似,区别在于 createdCallback() 回调:
var XFooProto = Object.create(HTMLElement.prototype);
XFooProto.createdCallback = function() {
// 1. 为元素附加一个 shadow root。
var shadow = this.createShadowRoot();
// 2. 填入标记。
shadow.innerHTML = "I'm in the element's Shadow DOM!";
};
var XFoo = document.registerElement('x-foo-shadowdom', {prototype: XFooProto});
从模板创建元素
I'm in Shadow DOM. My markup was stamped from a <template>.
为自定义元素增加样式
Do
Re
Mi
为使用 Shadow DOM 的元素增加样式
- Polymer 文档
- Shadow DOM 201 - CSS and Styling
使用 :unresolved 伪类避免无样式内容闪烁(FOUC)
使用 :unresolved 伪类避免无样式内容闪烁(FOUC)
注册后渐显的
:unresolved
伪类只能用于 unresolved
元素,而不能用于继承自 HTMLUnkownElement
的元素
I'm black because :unresolved doesn't apply to "panel".
It's not a valid custom element name.
I'm red because I match x-panel:unresolved.
历史和浏览器支持
检查 document.registerElement()
是否存在:
function supportsCustomElements() {
return 'registerElement' in document;
}
if (supportsCustomElements()) {
// Good to go!
} else {
// Use other libraries to create components.
}
参考
- Shadow DOM 101
- Shadow DOM 201 - CSS and Styling
- Shadow DOM 301 - CSS and Styling
- Custom Elements
- Polymer