HTML5 ShadowDOM & CustomElements
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 里。 当需要更新的时候,浏览器会自动保持它们的同步。
<template>
<style>
……
</style>
<div class="outer">
<div class="boilerplate">
Hi! My name is
</div>
<div class="name">
<content></content>
</div>
</div>
</template>
<script>
var shadow = document.querySelector('#nameTag').createShadowRoot();
var template = document.querySelector('#nameTagTemplate');
var clone = document.importNode(template.content, true);
shadow.appendChild(clone);
document.querySelector("#nameTag").textContent = "Shellie"
</script>
<div id="nameTag"></div>
通过select特性, 可以使用多个元素并控制投射元素
<!-- Shadow DOM -->
<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>
<!-- DOM -->
<div id="nameTag">
<div class="first">Bob</div>
<div>B. Love</div>
<div class="email">bob@</div>
</div>
Shadow DOM 样式
Shadow DOM定义的CSS样式只在Shadow Root下生效, 样式被封装起来
样式化宿主元素(host element)
:host样式化Shadow DOM元素, 并且无法影响到Shadow DOM外的元素
:host(x-bar:host) {
/* 当宿主是 <x-bar> 元素时生效。 */
}
:host(.different:host) {
/* 当宿主的类 <class="diffent"> 时生效。 */
}
: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 伪元素 穿过插入点来指定样式
<div>
<h3>Light DOM</h3>
<section>
<div>I'm not underlined</div>
<p>I'm underlined in Shadow DOM!</p>
</section>
</div>
<script>
var div = document.querySelector('div');
var root = div.createShadowRoot();
root.innerHTML = '\
<style>\
h3 { color: red; }\
content[select="h3"]::content > h3 {\
color: green;\
}\
::content section p {\
text-decoration: underline;\
}\
</style>\
<h3>Shadow DOM</h3>\
<content select="h3"></content>\
<content select="section"></content>';
</script>
对于一个 ShadowRoot 或 <shadow> 插入点:reset-style-inheritance 意味着可继承的 CSS 属性在宿主元素处被设置为 initial,此时这些属性还没有对 shadow 中的内容生效。该位置称为上边界(upper boundary)。
对于 <content> 插入点:reset-style-inheritance 意味着在宿主的子元素分发到插入点之前,将可继承的 CSS 属性设置为 initial。该位置称为下边界(lower boundary)。
使用多个shadowdom
最近添加的树称为 younger tree。之前添加的树称为 older tree。
添加进宿主元素中的 shadow 树按照它们的添加顺序而堆叠起来,从最先加入的 shadow 树开始。最终渲染的是最后加入的 shadow 树。
如果一个 shadow 树中存在多个 <shadow> 插入点,那么仅第一个被确认,其余的被忽略。
"Shadow 插入点" (<shadow>) 作为占位符可以插入 ShadowDOM
普通插入点 (<content>) 作为占位符可以插入 普通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 树
无法遍历 <content> 中的 DOM。.getDistributedNodes() 允许我们查询一个插入点的分布式节点:
<div id="example4">
<h2>Eric</h2>
<h2>Bidelman</h2>
<div>Digital Jedi</div>
<h4>footer text</h4>
</div>
<template id="sdom">
<header>
<content select="h2"></content>
</header>
<section>
<content select="div"></content>
</section>
<footer>
<content select="h4:first-of-type"></content>
</footer>
</template>
<script>
var container = document.querySelector('#example4');
var root = container.createShadowRoot();
var t = document.querySelector('#sdom');
var clone = document.importNode(t.content, true);
root.appendChild(clone);
var html = [];
[].forEach.call(root.querySelectorAll('content'), function(el) {
html.push(el.outerHTML + ': ');
var nodes = el.getDistributedNodes();
[].forEach.call(nodes, function(node) {
html.push(node.outerHTML);
});
html.push('\n');
});
</script>
可以在分布式节点上调用它的 .getDestinationInsertionPoints() 来查看它被分发进了哪个插入点中
<div id="host">
<h2>Light DOM</h2>
</div>
<script>
var container = document.querySelector('div');
var root1 = container.createShadowRoot();
var root2 = container.createShadowRoot();
root1.innerHTML = '<content select="h2"></content>';
root2.innerHTML = '<shadow></shadow>';
var h2 = document.querySelector('#host h2');
var insertionPoints = h2.getDestinationInsertionPoints();
[].forEach.call(insertionPoints, function(contentEl) {
console.log(contentEl);
});
</script>
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');
// <button is="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 = "<b>I'm an x-foo!</b>";
};
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 = "<b>I'm in the element's Shadow DOM!</b>";
};
var XFoo = document.registerElement('x-foo-shadowdom', {prototype: XFooProto});
从模板创建元素
<template id="sdtemplate">
<style>
p { color: orange; }
</style>
<p>I'm in Shadow DOM. My markup was stamped from a <template>.</p>
</template>
<script>
var proto = Object.create(HTMLElement.prototype, {
createdCallback: {
value: function() {
var t = document.querySelector('#sdtemplate');
var clone = document.importNode(t.content, true);
this.createShadowRoot().appendChild(clone);
}
}
});
document.registerElement('x-foo-from-template', {prototype: proto});
</script>
为自定义元素增加样式
<style>
app-panel {
display: flex;
}
[is="x-item"] {
transition: opacity 400ms ease-in-out;
opacity: 0.3;
flex: 1;
text-align: center;
border-radius: 50%;
}
[is="x-item"]:hover {
opacity: 1.0;
background: rgb(255, 0, 255);
color: white;
}
app-panel > [is="x-item"] {
padding: 5px;
list-style: none;
margin: 0 7px;
}
</style>
<app-panel>
<li is="x-item">Do</li>
<li is="x-item">Re</li>
<li is="x-item">Mi</li>
</app-panel>
为使用 Shadow DOM 的元素增加样式
使用 :unresolved 伪类避免无样式内容闪烁(FOUC)
使用 :unresolved 伪类避免无样式内容闪烁(FOUC)
注册后渐显的 <x-foo> 标签:
<style>
x-foo {
opacity: 1;
transition: opacity 300ms;
}
x-foo:unresolved {
opacity: 0;
}
</style>
:unresolved 伪类只能用于 unresolved 元素,而不能用于继承自 HTMLUnkownElement 的元素
<style>
/* 给所有 unresolved 元素添加边框 */
:unresolved {
border: 1px dashed red;
display: inline-block;
}
/* unresolved 元素 x-panel 的文本内容为红色 */
x-panel:unresolved {
color: red;
}
/* 定义注册后的 x-panel 文本内容为绿色 */
x-panel {
color: green;
display: block;
padding: 5px;
display: block;
}
</style>
<panel>
I'm black because :unresolved doesn't apply to "panel".
It's not a valid custom element name.
</panel>
<x-panel>I'm red because I match x-panel:unresolved.</x-panel>
历史和浏览器支持
检查 document.registerElement() 是否存在:
function supportsCustomElements() {
return 'registerElement' in document;
}
if (supportsCustomElements()) {
// Good to go!
} else {
// Use other libraries to create components.
}
jpg改rar 
HTML5 ShadowDOM & CustomElements的更多相关文章
- ShadowDOM
HTML5 ShadowDOM & CustomElements KeKeMars 关注 2015.12.09 15:20* 字数 1239 阅读 1626评论 2喜欢 2 Web组件由四部分 ...
- 【微信小程序项目实践总结】30分钟从陌生到熟悉 web app 、native app、hybrid app比较 30分钟ES6从陌生到熟悉 【原创】浅谈内存泄露 HTML5 五子棋 - JS/Canvas 游戏 meta 详解,html5 meta 标签日常设置 C#中回滚TransactionScope的使用方法和原理
[微信小程序项目实践总结]30分钟从陌生到熟悉 前言 我们之前对小程序做了基本学习: 1. 微信小程序开发07-列表页面怎么做 2. 微信小程序开发06-一个业务页面的完成 3. 微信小程序开发05- ...
- HTML5新特性:范围样式
原文出处:http://blog.csdn.net/hfahe/article/details/7381141 Chromium 最近实现了一个HTML5的新特性:范围样式,又叫做< ...
- web 自动化遇到 shadowDOM 节点你会操作吗?
本文转载自: http://www.lemfix.com/topics/971 近期有同学在做web自动化的时候,发现页面上有些元素,在selenium中无法通过xpath来定位,各种原因找了半天,都 ...
- Web自动化遇到shadowDOM节点操作(还没试)
近期有同学在做web自动化的时候,发现页面上有些元素,在selenium中无法通过xpath来定位,各种原因找了半天,都没找到解决方案. 最后发现元素在一个叫做shadow-root的节点下面. 如下 ...
- HTML5的新特性:范围样式,又叫做<style scoped>
Chromium 最近实现了一个HTML5的新特性:范围样式,又叫做<style scoped> .开发者可以通过为根元素设定一个添加了scoped属性的style标签,来限制样式只作用于 ...
- HTML5 & custom element & template
HTML5 & custom element & template template https://codepen.io/xgqfrms/pen/eYYExvp https://cs ...
- JS21. 使用原生JS封装一个公共的Alert插件(HTML5: Shadow Dom)
效果预览 Shadow DOM Web components 的一个重要属性是封装--可以将标记结构.样式和行为隐藏起来,并与页面上的其他代码相隔离,保证不同的部分不会混在一起,可使代码更加干净.整 ...
- 使用HTML5开发Kinect体感游戏
一.简介 我们要做的是怎样一款游戏? 在前不久成都TGC2016展会上,我们开发了一款<火影忍者手游>的体感游戏,主要模拟手游章节<九尾袭来 >,用户化身四代,与九尾进行对决, ...
随机推荐
- C语言 · 报时助手
基础练习 报时助手 时间限制:1.0s 内存限制:512.0MB 锦囊1 判断,字符串输出. 锦囊2 按要求输出,判断特殊情况. 问题描述 给定当前的时间,请用英文的读法 ...
- USB 之传输编码格式 NRZI 介绍
记录NRZI (Non-Return-to-Zero Inerted code) 非归零翻转编码,之前,我先稍微记录一下他的前身. RZ 编码(Return- to - zero coding) RZ ...
- history统计命令最多的20条
1.1.1 统计使用命令最多的20条 [root@ob1 ~]# history|awk '{ml[$2]++}END{for (i in ml) print i,ml[i]}'|sort -nrk ...
- 用 CSS 实现打印显示底色
上一篇有讲到如何在浏览器端实现打印功能.后面发现有个问题,就是表格表头有背景颜色,但是实际打印出来无背景颜色.网上的方法主要有以下几种实现方式: 1.把背景颜色写成行内样式,如下图所示: 但是发现这样 ...
- 用javascript将数据导入Excel
网上收集的代码 <input type="button" name="out_excel" onclick="AutomateExcel();& ...
- php 添加数据库的几种方法
最简单的 <?php $con = mysql_connect("localhost","root","root"); if (!$c ...
- 联合主键用hibernate注解映射方式主要有三种:
将联合主键的字段单独放在一个类中,该类需要实现java.io.Serializable接口并重写equals和hascode 第一.将该类注解为@Embeddable,最后在主类中(该类不包含联合主键 ...
- 【转】WCF入门教程三[WCF的宿主]
一.WCF服务应用程序与WCF服务库 我们在平时开发的过程中常用的项目类型有“WCF 服务应用程序”和“WCF服务库”. WCF服务应用程序,是一个可以执行的程序,它有独立的进程,WCF服务类契约的定 ...
- QT 交叉编译工具选择
使用QT交叉编译,生成的都是x86的可执行文件.Zoro告诉我交叉工具配置错了. 参考链接: http://www.cnblogs.com/zengjfgit/p/4744507.html linux ...
- 【复杂】CentOS 6.4下PXE+Kickstart无人值守安装操作系统
一.简介 1.1 什么是PXE PXE(Pre-boot Execution Environment,预启动执行环境)是由Intel公司开发的最新技术,工作于Client/Server的网络模式,支持 ...