摘要:向影子樹添加的任何內容都將成為宿主元素的本地元素,包括,這就是影子實現樣式作用域的方式。
這是專門探索 JavaScript 及其所構建的組件的系列文章的第 17 篇。
想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等著你!
如果你錯過了前面的章節,可以在這里找到它們:
JavaScript 是如何工作的:引擎,運行時和調用堆棧的概述!
JavaScript 是如何工作的:深入V8引擎&編寫優化代碼的5個技巧!
JavaScript 是如何工作的:內存管理+如何處理4個常見的內存泄漏!
JavaScript 是如何工作的:事件循環和異步編程的崛起+ 5種使用 async/await 更好地編碼方式!
JavaScript 是如何工作的:深入探索 websocket 和HTTP/2與SSE +如何選擇正確的路徑!
JavaScript 是如何工作的:與 WebAssembly比較 及其使用場景!
JavaScript 是如何工作的:Web Workers的構建塊+ 5個使用他們的場景!
JavaScript 是如何工作的:Service Worker 的生命周期及使用場景!
JavaScript 是如何工作的:Web 推送通知的機制!
JavaScript是如何工作的:使用 MutationObserver 跟蹤 DOM 的變化!
JavaScript是如何工作的:渲染引擎和優化其性能的技巧!
JavaScript是如何工作的:深入網絡層 + 如何優化性能和安全!
JavaScript是如何工作的:CSS 和 JS 動畫底層原理及如何優化它們的性能!
JavaScript的如何工作的:解析、抽象語法樹(AST)+ 提升編譯速度5個技巧!
JavaScript是如何工作的:深入類和繼承內部原理+Babel和 TypeScript 之間轉換!
JavaScript是如何工作的:存儲引擎+如何選擇合適的存儲API!
概述Web Components 是一套不同的技術,允許你創建可重用的定制元素,它們的功能封裝在你的代碼之外,你可以在 Web 應用中使用它們。
Web組件由四部分組成:
Shadow DOM(影子DOM)
HTML templates(HTML模板)
Custom elements(自定義元素)
HTML Imports(HTML導入)
在本文中主要講解 Shadow DOM(影子DOM)
Shadow DOM 這款工具旨在構建基于組件的應用。因此,可為網絡開發中的常見問題提供解決方案:
隔離 DOM:組件的 DOM 是獨立的(例如,document.querySelector() 不會返回組件 shadow DOM 中的節點)。
作用域 CSS:shadow DOM 內部定義的 CSS 在其作用域內。樣式規則不會泄漏,頁面樣式也不會滲入。
組合:為組件設計一個聲明性、基于標記的 API。
簡化 CSS - 作用域 DOM 意味著您可以使用簡單的 CSS 選擇器,更通用的 id/類名稱,而無需擔心命名沖突。
Shadow DOM本文假設你已經熟悉 DOM 及其它的 Api 的概念。如果不熟悉,可以在這里閱讀關于它的詳細文章—— https://developer.mozilla.org...。
陰影 DOM 只是一個普通的 DOM,除了兩個區別:
創建/使用的方式
與頁面其他部分有關的行為方式
通常,你創建 DOM 節點并將其附加至其他元素作為子項。 借助于 shadow DOM,您可以創建作用域 DOM 樹,該 DOM 樹附加至該元素上,但與其自身真正的子項分離開來。這一作用域子樹稱為影子樹。被附著的元素稱為影子宿主。 您在影子中添加的任何項均將成為宿主元素的本地項,包括
如果需要 Web 頁面上重復使用相同的標簽結構時,最好使用某種類型的模板,而不是一遍又一遍地重復相同的結構。這在以前也是可以實現,但是 HTML 元素(在現代瀏覽器中得到了很好的支持)使它變得容易得多。此元素及其內容不在 DOM 中渲染,但可以使用 JavaScript 引用它。
一個簡單的例子:
Paragraph content.
這不會出現在頁面中,直到使用 JavaScrip t引用它,然后使用如下方式將其追加到 DOM 中:
var template = document.getElementById("my-paragraph"); var templateContent = template.content; document.body.appendChild(templateContent);
到目前為止,已經有其他技術可以實現類似的行為,但是,正如前面提到的,將其原生封裝起來是非常好的,Templates 也有相當不錯的瀏覽器支持:
模板本身是有用的,但它們與自定義元素配合會更好。 可以 customElement Api 能定義一個自定義元素,并且告知 HTML 解析器如何正確地構造一個元素,以及在該元素的屬性變化時執行相應的處理。
讓我們定義一個 Web 組件名為
customElements.define("my-paragraph", class extends HTMLElement { constructor() { super(); let template = document.getElementById("my-paragraph"); let templateContent = template.content; const shadowRoot = this.attachShadow({mode: "open"}).appendChild(templateContent.cloneNode(true)); } });
這里需要注意的關鍵點是,我們向影子根添加了模板內容的克隆,影子根是使用 Node.cloneNode() 方法創建的。
因為將其內容追加到一個 Shadow DOM 中,所以可以在模板中使用
Paragraph content.
現在自定義組件可以這樣使用:
模板有一些缺點,主要是靜態內容,它不允許我們渲染變量/數據,好可以讓我們按照一般使用的標準 HTML 模板的習慣來編寫代碼。Slot 是組件內部的占位符,用戶可以使用自己的標記來填充。讓我們看看上面的模板怎么使用 slot :
Default text
如果在標記中包含元素時沒有定義插槽的內容,或者瀏覽器不支持插槽,
為了定義插槽的內容,應該在
Let"s have some different text!
可以插入插槽的元素稱為 Slotable; 當一個元素插入一個插槽時,它被稱為開槽 (slotted)。
注意,在上面的例子中,插入了一個 元素,它是一個開槽元素,它有一個屬性 slot,它等于 my-text,與模板中的 slot 定義中的 name 屬性的值相同。
在瀏覽器中渲染后,上面的代碼將構建以下扁平 DOM 樹:
設定樣式#shadow-root
Let"s have some different text!
使用 shadow DOM 的組件可通過主頁來設定樣式,定義其自己的樣式或提供鉤子(以 CSS 自定義屬性的形式)讓用戶替換默認值。
組件定義的樣式作用域 CSS 是 Shadow DOM 最大的特性之一:
外部頁面的 CSS 選擇器不應用于組件內部
組件內定義的樣式不會影響頁面的其他元素,它們的作用域是宿主元素
shadow DOM 內部使用的 CSS 選擇器在本地應用于組件實際上,這意味著我們可以再次使用公共vid/類名,而不用擔心頁面上其他地方的沖突,最佳做法是在 Shadow DOM 內使用更簡單的 CSS 選擇器,它們在性能上也不錯。
看看在 #shadow-root 定義了一些樣式的:
#shadow-root
上面例子中的所有樣式都是#shadow-root的本地樣式。使用元素在#shadow-root中引入樣式表,這些樣式表也都屬于本地的。
:host 偽類選擇器使用 :host 偽類選擇器,用來選擇組件宿主元素中的元素 (相對于組件模板內部的元素)。
當涉及到 :host 選擇器時,應該小心一件事:父頁面中的規則具有比元素中定義的 :host 規則具有更高的優先級,這允許用戶從外部覆蓋頂級樣式。而且 :host 只在影子根目錄下工作,所以你不能在Shadow DOM 之外使用它。
如果 :host(
:host-context(
:host-context(
比如,很多人都通過將類應用到 或 進行主題化:
…
在下面的例子中,只有當某個祖先元素有 CSS 類theme-light時,我們才會把background-color樣式應用到組件內部的所有元素中:
:host-context(.theme-light) h2 { background-color: #eef; }/deep/
組件樣式通常只會作用于組件自身的 HTML 上,我們可以使用 /deep/ 選擇器,來強制一個樣式對各級子組件的視圖也生效,它不但作用于組件的子視圖,也會作用于組件的內容。
在下面例子中,我們以所有的元素為目標,從宿主元素到當前元素再到 DOM 中的所有子元素:
:host /deep/ h3 { font-style: italic; }
/deep/ 選擇器還有一個別名 >>>,可以任意交替使用它們。
/deep/ 和 >>> 選擇器只能被用在仿真 (emulated)模式下。 這種方式是默認值,也是用得最多的方式。從外部為組件設定樣式
有幾種方法可從外部為組件設定樣式:最簡單的方法是使用標記名稱作為選擇器,如下
custom-container { color: red; }
外部樣式比在 Shadow DOM 中定義的樣式具有更高的優先級。
例如,如果用戶編寫選擇器:
custom-container { width: 500px; }
它將覆蓋組件的樣式:
:host { width: 300px; }
對組件本身進行樣式化只能到此為止。但是如果人想要對組件的內部進行樣式化,會發生什么情況呢?為此,我們需要 CSS 自定義屬性。
使用 CSS 自定義屬性創建樣式鉤子如果組件的開發者通過 CSS 自定義屬性提供樣式鉤子,則用戶可調整內部樣式。其思想類似于
看看下面的例子:
…
在其 shadow DOM 內部:
:host([background]) { background: var(?-?custom-container-bg, #CECECE); border-radius: 10px; padding: 10px; }
在本例中,該組件將使用 black 作為背景值,因為用戶指定了該值,否則,背景顏色將采用默認值 #CECECE。
作為組件的作者,是有責任讓開發人員了解他們可以使用的 CSS 定制屬性,將其視為組件的公共接口的一部分。在 JS 中使用 slot
Shadow DOM API 提供了使用 slot 和分布式節點的實用程序,這些實用程序在編寫自定義元素時遲早派得上用場。
slotchange 事件當 slot 的分布式節點發生變化時,slotchange 事件將觸發。例如,如果用戶從 light DOM 中添加/刪除子元素。
var slot = this.shadowRoot.querySelector("#some_slot"); slot.addEventListener("slotchange", function(e) { console.log("Light DOM change"); });
要監視對 light DOM 的其他類型的更改,可以在元素的構造函數中使用 MutationObserver。以前討論過 MutationObserver 的內部結構以及如何使用它。
assignedNodes() 方法有時候,了解哪些元素與 slot 相關聯非常有用。調用 slot.assignedNodes() 可查看 slot 正在渲染哪些元素。 {flatten: true} 選項將返回 slot 的備用內容(前提是沒有分布任何節點)。
讓我們看看下面的例子:
Default content
假設這是在一個名為
看看這個組件的不同用法,以及調用 assignedNodes() 的結果是什么:
在第一種情況下,我們將向 slot 中添加我們自己的內容:
container text
調用 assignedNodes() 會得到 [ container text ],注意,結果是一個節點數組。
在第二種情況下,將內容置空:
調用 assignedNodes() 的結果將返回一個空數組 []。
在第三種情況下,調用 slot.assignedNodes({flatten: true}),得到結果是: [
默認內容
]。此外,要訪問 slot 中的元素,可以調用 assignedNodes() 來查看元素分配給哪個組件 slot。
事件模型值得注意的是,當發生在 Shadow DOM 中的事件冒泡時,會發生什么。
當事件從 Shadow DOM 中觸發時,其目標將會調整為維持 Shadow DOM 提供的封裝。也就是說,事件的目標重新進行了設定,因此這些事件看起來像是來自組件,而不是來自 Shadow DOM 中的內部元素。
下面是從 Shadow DOM 傳播出去的事件列表(有些沒有):
聚焦事件:blur、focus、focusin、focusout
鼠標事件:click、dblclick、mousedown、mouseenter、mousemove,等等
滾輪事件:wheel
輸入事件:beforeinput、input
鍵盤事件:keydown、keyup
組合事件:compositionstart、compositionupdate、compositionend
拖放事件:dragstart、drag、dragend、drop,等等
自定義事件默認情況下,自定義事件不會傳播到 Shadow DOM 之外。如果希望分派自定義事件并使其傳播,則需要添加 bubbles: true 和 composed: true 選項。
讓我們看看派發這樣的事件是什么樣的:
var container = this.shadowRoot.querySelector("#container"); container.dispatchEvent(new Event("containerchanged", {bubbles: true, composed: true}));瀏覽器支持
如希望獲得 shadow DOM 檢測功能,請查看是否存在 attachShadow:
const supportsShadowDOMV1 = !!HTMLElement.prototype.attachShadow;
有史以來第一次,我們擁有了實施適當 CSS 作用域、DOM 作用域的 API 原語,并且有真正意義上的組合。 與自定義元素等其他網絡組件 API 組合后,shadow DOM 提供了一種編寫真正封裝組件的方法,無需花多大的功夫或使用如
代碼部署后可能存在的BUG沒法實時知道,事后為了解決這些BUG,花了大量的時間進行log 調試,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug。
你的點贊是我持續分享好東西的動力,歡迎點贊!
歡迎加入前端大家庭,里面會經常分享一些技術資源。文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/108898.html
摘要:此即如何實現局部樣式化的原理。這是一個絕佳的方式,開發者可以在組件內部封裝響應用戶交互或者狀態的行為,然后基于宿主元素來樣式化內部節點。 原文請查閱這里,略有刪減,本文采用知識共享署名 4.0 國際許可協議共享,BY Troland。 這是 JavaScript 工作原理的第十七章。 showImg(https://segmentfault.com/img/remote/1460000...
摘要:為了方便大家共同學習,整理了之前博客系列的文章,目前已整理是如何工作這個系列,可以請猛戳博客查看。以下列出該系列目錄,歡迎點個星星,我將更友動力整理理優質的文章,一起學習。 為了方便大家共同學習,整理了之前博客系列的文章,目前已整理 JavaScript 是如何工作這個系列,可以請猛戳GitHub博客查看。 以下列出該系列目錄,歡迎點個星星,我將更友動力整理理優質的文章,一起學習。 J...
摘要:與大多數全局對象不同,沒有構造函數。為什么要設計更加有用的返回值早期寫法寫法函數式操作早期寫法寫法可變參數形式的構造函數一般寫法寫法當然還有很多,大家可以自行到上查看什么是代理設計模式代理模式,為其他對象提供一種代理以控制對這個對象的訪問。 這是專門探索 JavaScript 及其所構建的組件的系列文章的第 19 篇。 如果你錯過了前面的章節,可以在這里找到它們: 想閱讀更多優質文章請...
Create by jsliang on 2019-2-11 15:30:34 Recently revised in 2019-3-17 21:30:36 Hello 小伙伴們,如果覺得本文還不錯,記得給個 star , 小伙伴們的 star 是我持續更新的動力!GitHub 地址 并不是只有特定的季節才能跑路,只因為人跑得多了,這條路就定下來了。 金三銀四跳槽季,jsliang 于 2019...
摘要:掛機科了次使用這個結構,匿名函數就有了自己的執行環境或閉包,然后我們立即執行。注意,匿名函數的圓括號是必需的,因為以關鍵字開頭的語句通常被認為是函數聲明請記住,中不能使用未命名的函數聲明。 這是專門探索 JavaScript 及其所構建的組件的系列文章的第 20 篇。 想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等著你! 如果你錯過了前面的章節,可以在這里找到它們: ...
閱讀 1155·2023-04-25 17:28
閱讀 3531·2021-10-14 09:43
閱讀 3954·2021-10-09 10:02
閱讀 1941·2019-08-30 14:04
閱讀 3127·2019-08-30 13:09
閱讀 3268·2019-08-30 12:53
閱讀 2895·2019-08-29 17:11
閱讀 1822·2019-08-29 16:58