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