Web组件由四部分组成javascript
Shadow DOM
能够和一个根节点Shadow root
关联, 该Shadow DOM
元素称为Shadow Host
内容不会被渲染, 而Shadow root
内容会被渲染。css
可是,内容不该该放进Shadow DOM
内, 以便被搜索引擎
阅读器
等访问到, 可重用部件无心义的标记应该放进Shadow DOM
中html
内容在文档内;展示在 Shadow DOM
里。 当须要更新的时候,浏览器会自动保持它们的同步。html5
<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
特性, 可使用多个元素并控制投射元素java
<!-- 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
定义的CSS
样式只在Shadow Root
下生效, 样式被封装起来node
:host
样式化Shadow DOM
元素, 而且没法影响到Shadow DOM
外的元素git
:host(x-bar:host) { /* 当宿主是 <x-bar> 元素时生效。 */ } :host(.different:host) { /* 当宿主的类 <class="diffent"> 时生效。 */ } :host:hover { /* 当鼠标放置到宿主上时生效。 */ opacity: 1; }
^
链接符等价于后代选择器(例如 div p {...}),只不过它能跨越 一个 shadow 边界。^^
后代选择器可以跨越 任意数量的 shadow 边界。querySelector()
支持该选择器web
video ^ input[type="range"] { background: hotpink; }
var root = document.querySelector('div').createShadowRoot(); root.resetStyleInheritance = false;
{
reset-style-inheritance: true; }
在插入点, 选择是否继承上级样式(只影响可继承的样式)浏览器
<div> <h3>Light DOM</h3> <section> <div>I'm not underlined</div> <p>I'm underlined in Shadow DOM!</p> </section> </div> <script>
对于一个 ShadowRoot 或 <shadow>
插入点:reset-style-inheritance 意味着可继承的 CSS 属性在宿主元素处被设置为 initial,此时这些属性尚未对 shadow 中的内容生效。该位置称为上边界(upper boundary)。app
对于 <content>
插入点:reset-style-inheritance 意味着在宿主的子元素分发到插入点以前,将可继承的 CSS 属性设置为 initial。该位置称为下边界(lower boundary)。
最近添加的树称为 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) { } });
可使用 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
事件会被重定向,使它看起来是从宿主元素上发出,而并不是是 Shadow DOM 的内部元素。(event.path
来查看调整后的事件路径。)
如下事件永远没法越过 shadow 边界:
document.registerElement()
能够建立一个自定义元素
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">
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 建立元素,跟建立一个渲染基础标记的元素很是相似,区别在于 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>
使用 :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. }