ShadowDOM

HTML5 ShadowDOM & CustomElements

96 
KeKeMars 
2015.12.09 15:20* 字數 1239 閱讀 1626評論 2

Web組件由四部分組成javascript

  • Template
  • Shadow DOM (Chrome Opera支持)
  • Custom Elements
  • Packaging

Shadow DOM 組成

Shadow DOM能夠和一個根節點Shadow root關聯, 該Shadow DOM元素稱爲Shadow Host內容不會被渲染, 而Shadow root內容會被渲染。css

可是,內容不該該放進Shadow DOM內, 以便被搜索引擎 閱讀器等訪問到, 可重用部件無心義的標記應該放進Shadow DOMhtml

Shadow DOM從展示中分離細節

內容在文檔內;展示在 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 樣式

Shadow DOM定義的CSS樣式只在Shadow Root下生效, 樣式被封裝起來node

樣式化宿主元素(host element)

:host樣式化Shadow DOM元素, 而且沒法影響到Shadow DOM外的元素git

:host(x-bar:host) { /* 當宿主是 <x-bar> 元素時生效。 */ } :host(.different:host) { /* 當宿主的類 <class="diffent"> 時生效。 */ } :host:hover { /* 當鼠標放置到宿主上時生效。 */ opacity: 1; } 

^(Hat) 和 ^^(Cat)選擇器

^ 鏈接符等價於後代選擇器(例如 div p {...}),只不過它能跨越 一個 shadow 邊界。
^^ 後代選擇器可以跨越 任意數量的 shadow 邊界。
querySelector()支持該選擇器web

能夠經過 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)。app

對於 <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 &lt;template&gt;.</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. } 

參考

相關文章
相關標籤/搜索