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. }