明輝手游網(wǎng)中心:是一個免費提供流行視頻軟件教程、在線學習分享的學習平臺!

你知道原生HTML組件是什么嗎?原生HTML組件的說明

[摘要]本篇文章給大家?guī)淼膬热菔顷P于你知道原生HTML組件是什么嗎?原生HTML組件的介紹,有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。嘿!看看這幾年啊,Web 前端的發(fā)展可是真快。∠胂霂啄昵,HTML 是前端開發(fā)者的基本技能,通過各式各樣的標簽就可以搭建一個可用的網(wǎng)站,基本交互也不...
本篇文章給大家?guī)淼膬热菔顷P于你知道原生HTML組件是什么嗎?原生HTML組件的介紹,有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。

嘿!看看這幾年啊,Web 前端的發(fā)展可是真快!

想想幾年前,HTML 是前端開發(fā)者的基本技能,通過各式各樣的標簽就可以搭建一個可用的網(wǎng)站,基本交互也不是問題。如果再來點 CSS,嗯,金黃酥脆,美味可口。這時候再撒上幾把 JavaScript,簡直讓人欲罷不能。

隨著需求的增長,HTML 的結構越來越復雜,大量重復的代碼使得頁面改動起來異常困難,這也就孵化了一批批模版工具,將公共的部分抽取出來變?yōu)楣步M件。再后來,隨著 JavaScript 的性能提升,JavaScript 的地位越來越高,不再只是配菜了,前端渲染的出現(xiàn)降低了服務端解析模版的壓力,服務端只要提供靜態(tài)文件和 API 接口就行了嘛。再然后,前端渲染工具又被搬回了服務端,后端渲染出現(xiàn)了(黑人問號???)

總之,組件化使得復雜的前端結構變得清晰,各個部分獨立起來,高內聚低耦合,使得維護成本大大降低。

那么,你有聽說過原生 HTML 組件嗎?

1754597496-5bc7f1ee5c154_articlex.jpg

四大 Web 組件標準

在說原生 HTML 組件之前,要先簡單介紹一下四大 Web 組件標準,四大 Web 組件標準分別為:HTML Template、Shadow DOM、Custom Elements 和 HTML Imports。實際上其中一個已經被廢棄了,所以變成“三大”了。

HTML Template 相信很多人都有所耳聞,簡單的講也就是 HTML5 中的 <template> 標簽,正常情況下它無色無味,感知不到它的存在,甚至它下面的 img 都不會被下載,script 都不會被執(zhí)行。<template> 就如它的名字一樣,它只是一個模版,只有到你用到它時,它才會變得有意義。

Shadow DOM 則是原生組件封裝的基本工具,它可以實現(xiàn)組件與組件之間的獨立性。

Custom Elements 是用來包裝原生組件的容器,通過它,你就只需要寫一個標簽,就能得到一個完整的組件。

HTML Imports 則是 HTML 中類似于 ES6 Module 的一個東西,你可以直接 import 另一個 html 文件,然后使用其中的 DOM 節(jié)點。但是,由于 HTML Imports 和 ES6 Module 實在是太像了,并且除了 Chrome 以外沒有瀏覽器愿意實現(xiàn)它,所以它已經被廢棄并不推薦使用了。未來會使用 ES6 Module 來取代它,但是現(xiàn)在貌似還沒有取代的方案,在新版的 Chrome 中這個功能已經被刪除了,并且在使用的時候會在 Console 中給出警告。警告中說使用 ES Modules 來取代,但是我測試在 Chrome 71 中 ES Module 會強制檢測文件的 MIME 類型必須為 JavaScript 類型,應該是暫時還沒有實現(xiàn)支持。

2508713701-5bc7f09060787_articlex.png

Shadow DOM

要說原生 HTML 組件,就要先聊聊 Shadow DOM 到底是個什么東西。

大家對 DOM 都很熟悉了,在 HTML 中作為一個最基礎的骨架而存在,它是一個樹結構,樹上的每一個節(jié)點都是 HTML 中的一部分。DOM 作為一棵樹,它擁有著上下級的層級關系,我們通常使用“父節(jié)點”、“子節(jié)點”、“兄弟節(jié)點”等來進行描述(當然有人覺得這些稱謂強調性別,所以也創(chuàng)造了一些性別無關的稱謂)。子節(jié)點在一定程度上會繼承父節(jié)點的一些東西,也會因兄弟節(jié)點而產生一定的影響,比較明顯的是在應用 CSS Style 的時候,子節(jié)點會從父節(jié)點那里繼承一些樣式。

而 Shadow DOM,也是 DOM 的一種,所以它也是一顆樹,只不過它是長在 DOM 樹上的一棵特殊的紫薯,啊不,子樹。

什么?DOM 本身不就是由一棵一棵的子樹組成的嗎?這個 Shadow DOM 有什么特別的嗎?

Shadow DOM 的特別之處就在于它致力于創(chuàng)建一個相對獨立的一個空間,雖然也是長在 DOM 樹上的,但是它的環(huán)境卻是與外界隔離的,當然這個隔離是相對的,在這個隔離空間中,你可以選擇性地從 DOM 樹上的父節(jié)點繼承一些屬性,甚至是繼承一棵 DOM 樹進來。

利用 Shadow DOM 的隔離性,我們就可以創(chuàng)造原生的 HTML 組件了。

實際上,瀏覽器已經通過 Shadow DOM 實現(xiàn)了一些組件了,只是我們使用過卻沒有察覺而已,這也是 Shadow DOM 封裝的組件的魅力所在:你只管寫一個 HTML 標簽,其他的交給我。(是不是有點像 React 的 JSX 啊?)

我們來看一看瀏覽器利用 Shadow DOM 實現(xiàn)的一個示例吧,那就是 video 標簽:

<video controls src="./video.mp4" width="400" height="300"></video>

我們來看一下瀏覽器渲染的結果:

3986383008-5bc7f0af9a354_articlex.png

等一下!不是說 Shadow DOM 嗎?這和普通 DOM 有啥區(qū)別???

在 Chrome 中,Elements 默認是不顯示內部實現(xiàn)的 Shadow DOM 節(jié)點的,需要在設置中啟用:

4254440722-5bc7f0c9331b5_articlex.png

3551890025-5bc7f0d516ea6_articlex.png

注:瀏覽器默認隱藏自身的 Shadow DOM 實現(xiàn),但如果是用戶通過腳本創(chuàng)造的 Shadow DOM,是不會被隱藏的。

然后,我們就可以看到 video 標簽的真面目了:

925947494-5bc7f0fadca05_articlex.png

在這里,你可完全像調試普通 DOM 一樣隨意調整 Shadow DOM 中的內容(反正和普通 DOM 一樣,刷新一下就恢復了)。

我們可以看到上面這些 shadow DOM 中的節(jié)點大多都有 pseudo 屬性,根據(jù)這個屬性,你就可以在外面編寫 CSS 樣式來控制對應的節(jié)點樣式了。比如,將上面這個 pseudo="-webkit-media-controls-overlay-play-button" 的 input 按鈕的背景色改為橙色:

video::-webkit-media-controls-overlay-play-button {
  background-color: orange;
}

2935846866-5bc7f11a17342_articlex.png

由于 Shadow DOM 實際上也是 DOM 的一種,所以在 Shadow DOM 中還可以繼續(xù)嵌套 Shadow DOM,就像上面那樣。

瀏覽器中還有很多 Element 都使用了 Shadow DOM 的形式進行封裝,比如 <input>、<select>、<audio> 等,這里就不一一展示了。

由于 Shadow DOM 的隔離性,所以即便是你在外面寫了個樣式:div { background-color: red !important; },Shadow DOM 內部的 div 也不會受到任何影響

也就是說,寫樣式的時候,該用 id 的時候就用 id,該用 class 的時候就用 class,一個按鈕的 class 應該寫成 .button 就寫成 .button。完全不用考慮當前組件中的 id、class 可能會與其他組件沖突,你只要確保一個組件內部不沖突就好——這很容易做到。

這解決了現(xiàn)在絕大多數(shù)的組件化框架都面臨的問題:Element 的 class(className) 到底怎么寫?用前綴命名空間的形式會導致 class 名太長,像這樣:.header-nav-list-sublist-button-icon;而使用一些 CSS-in-JS 工具,可以創(chuàng)造一些唯一的 class 名稱,像這樣:.Nav__welcomeWrapper___lKXTg,這樣的名稱仍舊有點長,還帶了冗余信息。

ShadowRoot

ShadowRoot 是 Shadow DOM 下面的根,你可以把它當做 DOM 中的 <body> 一樣看待,但是它不是 <body>,所以你不能使用 <body> 上的一些屬性,甚至它不是一個節(jié)點。

你可以通過 ShadowRoot 下面的 appendChild、querySelectorAll 之類的屬性或方法去操作整個 Shadow DOM 樹。

對于一個普通的 Element,比如 <p>,你可以通過調用它上面的 attachShadow 方法來創(chuàng)建一個 ShadowRoot(還有一個 createShadowRoot 方法,已經過時不推薦使用),attachShadow 接受一個對象進行初始化:{ mode: 'open' },這個對象有一個 mode 屬性,它有兩個取值:'open' 和 'closed',這個屬性是在創(chuàng)造 ShadowRoot 的時候需要初始化提供的,并在創(chuàng)建 ShadowRoot 之后成為一個只讀屬性。

mode: 'open' 和 mode: 'closed' 有什么區(qū)別呢?在調用 attachShadow 創(chuàng)建 ShadowRoot 之后,attachShdow 方法會返回 ShadowRoot 對象實例,你可以通過這個返回值去構造整個 Shadow DOM。當 mode 為 'open' 時,在用于創(chuàng)建 ShadowRoot 的外部普通節(jié)點(比如 <p>)上,會有一個 shadowRoot 屬性,這個屬性也就是創(chuàng)造出來的那個 ShadowRoot,也就是說,在創(chuàng)建 ShadowRoot 之后,還是可以在任何地方通過這個屬性再得到 ShadowRoot,繼續(xù)對其進行改造;而當 mode 為 'closed' 時,你將不能再得到這個屬性,這個屬性會被設置為 null,也就是說,你只能在 attachShadow 之后得到 ShadowRoot 對象,用于構造整個 Shadow DOM,一旦你失去對這個對象的引用,你就無法再對 Shadow DOM 進行改造了。

可以從上面 Shadow DOM 的截圖中看到 #shadow-root (user-agent) 的字樣,這就是 ShadowRoot 對象了,而括號中的 user-agent 表示這是瀏覽器內部實現(xiàn)的 Shadow DOM,如果使用通過腳本自己創(chuàng)建的 ShadowRoot,括號中會顯示為 open 或 closed 表示 Shadow DOM 的 mode。

3465801305-5bc7f119096b3_articlex.png

瀏覽器內部實現(xiàn)的 user-agent 的 mode 為 closed,所以你不能通過節(jié)點的 ShadowRoot 屬性去獲得其 ShadowRoot 對象,也就意味著你不能通過腳本對這些瀏覽器內部實現(xiàn)的 Shadow DOM 進行改造。

HTML Template

有了 ShadowRoot 對象,我們可以通過代碼來創(chuàng)建內部結構了,對于簡單的結構,也許我們可以直接通過 document.createElement 來創(chuàng)建,但是稍微復雜一些的結構,如果全部都這樣來創(chuàng)建不僅麻煩,而且代碼可讀性也很差。當然也可以通過 ES6 提供的反引號字符串(const template = `......`;)配合 innerHTML 來構造結構,利用反引號字符串中可以任意換行,并且 HTML 對縮進并不敏感的特性來實現(xiàn)模版,但是這樣也是不夠優(yōu)雅,畢竟代碼里大段大段的 HTML 字符串并不美觀,即便是單獨抽出一個常量文件也是一樣。

這個時候就可以請 HTML Template 出場了。我們可以在 html 文檔中編寫 DOM 結構,然后在 ShadowRoot 中加載過來即可。

HTML Template 實際上就是在 html 中的一個 <template> 標簽,正常情況下,這個標簽下的內容是不會被渲染的,包括標簽下的 img、style、script 等都是不會被加載或執(zhí)行的。你可以在腳本中使用 getElementById 之類的方法得到 <template> 標簽對應的節(jié)點,但是卻無法直接訪問到其內部的節(jié)點,因為默認他們只是模版,在瀏覽器中表現(xiàn)為 #document-fragment,字面意思就是“文檔片段”,可以通過節(jié)點對象的 content 屬性來訪問到這個 document-fragment 對象。

2033065598-5bc7f18543064_articlex.png

通過 document-fragment 對象,就可以訪問到 template 內部的節(jié)點了,通過 document.importNode 方法,可以將 document-fragment 對象創(chuàng)建一份副本,然后可以使用一切 DOM 屬性方法替換副本中的模版內容,最終將其插入到 DOM 或是 Shadow DOM 中。

<div id="div"></div>
<template id="temp">
  <div id="title"></div>
</template>
const template = document.getElementById('temp');
const copy = document.importNode(template.content, true);
copy.getElementById('title').innerHTML = 'Hello World!';

const div = document.getElementById('div');
const shadowRoot = div.attachShadow({ mode: 'closed' });
shadowRoot.appendChild(copy);

HTML Imports

有了 HTML Template,我們已經可以方便地創(chuàng)造封閉的 Web 組件了,但是目前還有一些不完美的地方:我們必須要在 html 中定義一大批的 <template>,每個組件都要定義一個 <template>。

此時,我們就可以用到已經被廢棄的 HTML Imports 了。雖然它已經被廢棄了,但是未來會通過 ES6 Modules 的形式再進行支持,所以理論上也只是換個加載形式而已。

通過 HTML Imports,我們可以將 <template> 定義在其他的 html 文檔中,然后再在需要的 html 文檔中進行導入(當然也可以通過腳本按需導入),導入后,我們就可以直接使用其中定義的模版節(jié)點了。

已經廢棄的 HTML Imports 通過 <link> 標簽實現(xiàn),只要指定 rel="import" 就可以了,就像這樣:<link rel="import" href="./templates.html">,它可以接受 onload 和 onerror 事件以指示它已經加載完成。當然也可以通過腳本來創(chuàng)建 link 節(jié)點,然后指定 rel 和 href 來按需加載。Import 成功后,在 link 節(jié)點上有一個 import 屬性,這個屬性中存儲的就是 import 進來的 DOM 樹啦,可以 querySelector 之類的,并通過 cloneNode 或 document.importNode 方法創(chuàng)建副本后使用。

未來新的 HTML Imports 將會以 ES6 Module 的形式提供,可以在 JavaScript 中直接 import * as template from './template.html';,也可以按需 import,像這樣:const template = await import('./template.html');。不過目前雖然瀏覽器都已經支持 ES6 Modules,但是在 import 其他模塊時會檢查服務端返回文件的 MIME 類型必須為 JavaScript 的 MIME 類型,否則不允許加載。

Custom Elements

有了上面的三個組件標準,我們實際上只是對 HTML 進行拆分而已,將一個大的 DOM 樹拆成一個個相互隔離的小 DOM 樹,這還不是真正的組件。

要實現(xiàn)一個真正的組件,我們就需要用到 Custom Elements 了,就如它的名字一樣,它是用來定義原生組件的。

Custom Elements 的核心,實際上就是利用 JavaScript 中的對象繼承,去繼承 HTML 原生的 HTMLElement 類(或是具體的某個原生 Element 類,比如 HTMLButtonElement),然后自己編寫相關的生命周期函數(shù),處理成員屬性以及用戶交互的事件。

看起來這和現(xiàn)在的 React 很像,在 React 中,你可以這樣創(chuàng)造一個組件:class MyElement extends React.Component { ... },而使用原生 Custom Elements,你需要這樣寫:class MyElement extends HTMLElement { ... }。

Custom Elements 的生命周期函數(shù)并不多,但是足夠使用。這里我將 Custom Elements 的生命周期函數(shù)與 React 進行一個簡單的對比:

constructor(): 構造函數(shù),用于初始化 state、創(chuàng)建 Shadow DOM、監(jiān)聽事件之類。

對應 React 中 Mounting 階段的大半部分,包括:constructor(props)、static getDerivedStateFromProps(props, state) 和 render()。

在 Custom Elements 中,constructor() 構造函數(shù)就是其原本的含義:初始化,和 React 的初始化類似,但它沒有像 React 中那樣將其拆分為多個部分。在這個階段,組件僅僅是被創(chuàng)建出來(比如通過 document.createElement()),但是還沒有插入到 DOM 樹中。

connectedCallback(): 組件實例已被插入到 DOM 樹中,用于進行一些展示相關的初始化操作。

對應 React 中 Mounting 階段的最后一個生命周期:componentDidMount()。

在這個階段,組件已經被插入到 DOM 樹中了,或是其本身就在 html 文件中寫好在 DOM 樹上了,這個階段一般是進行一些展示相關的初始化,比如加載數(shù)據(jù)、圖片、音頻或視頻之類并進行展示。

attributeChangedCallback(attrName, oldVal, newVal): 組件屬性發(fā)生變化,用于更新組件的狀態(tài)。

對應 React 中的 Updating 階段:static getDerivedStateFromProps(props, state)、shouldComponentUpdate(nextProps, nextState)、render()、getSnapshotBeforeUpdate(prevProps, prevState) 和 componentDidUpdate(prevProps, prevState, snapshot)。

當組件的屬性(React 中的 props)發(fā)生變化時觸發(fā)這個生命周期,但是并不是所有屬性變化都會觸發(fā),比如組件的 class、style 之類的屬性發(fā)生變化一般是不會產生特殊交互的,如果所有屬性發(fā)生變化都觸發(fā)這個生命周期的話,會使得性能造成較大的影響。所以 Custom Elements 要求開發(fā)者提供一個屬性列表,只有當屬性列表中的屬性發(fā)生變化時才會觸發(fā)這個生命周期函數(shù)。

這個屬性列表通過組件類上的一個靜態(tài)只讀屬性來聲明,在 ES6 Class 中使用一個 getter 函數(shù)來實現(xiàn),只實現(xiàn) getter 而不實現(xiàn) setter,getter 返回一個常量,這樣就是只讀的了。像這樣:

class AwesomeElement extends HTMLElement {
  static get observedAttributes() {
    return ['awesome'];
  }
}

disconnectedCallback(): 組件被從 DOM 樹中移除,用于進行一些清理操作。

對應 React 中的 Unmounting 階段:componentWillUnmount()。

adoptedCallback(): 組件實例從一個文檔被移動到另一個文檔。

這個生命周期是原生組件獨有的,React 中沒有類似的生命周期。這個生命周期函數(shù)也并不常用到,一般在操作多個 document 的時候會遇到,調用 document.adoptNode() 函數(shù)轉移節(jié)點所屬 document 時會觸發(fā)這個生命周期。

在定義了自定義組件后,我們需要將它注冊到 HTML 標簽列表中,通過 window.customElements.define() 函數(shù)即可實現(xiàn),這個函數(shù)接受兩個必須參數(shù)和一個可選參數(shù)。第一個參數(shù)是注冊的標簽名,為了避免和 HTML 自身的標簽沖突,Custom Elements 要求用戶自定義的組件名必須至少包含一個短杠 -,并且不能以短杠開頭,比如 my-element、awesome-button 之類都是可以的。第二個參數(shù)是注冊的組件的 class,直接將繼承的子類類名傳入即可,當然也可以直接寫一個匿名類:

window.customElements.define('my-element', class extends HTMLElement {
  ...
});

注冊之后,我們就可以使用了,可以直接在 html 文檔中寫對應的標簽,比如:<my-element></my-element>,也可以通過 document.createElement('my-element') 來創(chuàng)建,用法與普通標簽幾乎完全一樣。但要注意的是,雖然 html 標準中說部分標簽可以不關閉或是自關閉(<br> 或是 <br />),但是只有規(guī)定的少數(shù)幾個標簽允許自關閉,所以,在 html 中寫 Custom Elements 的節(jié)點時必須帶上關閉標簽。

由于 Custom Elements 是通過 JavaScript 來定義的,而一般 js 文件都是通過 <script> 標簽外聯(lián)的,所以 html 文檔中的 Custom Elements 在 JavaScript 未執(zhí)行時是處于一個默認的狀態(tài),瀏覽器默認會將其內容直接顯示出來。為了避免這樣的情況發(fā)生,Custom Elements 在被注冊后都會有一個 :defined CSS 偽類而在注冊前沒有,所以我們可以通過 CSS 選擇器在 Custom Elements 注冊前將其隱藏起來,比如:

my-element:not(:defined) {
  display: none;
}

或者 Custom Elements 也提供了一個函數(shù)來檢測指定的組件是否已經被注冊:customElements.whenDefined(),這個函數(shù)接受一個組件名參數(shù),并返回一個 Promise,當 Promise 被 resolve 時,就表示組件被注冊了。

這樣,我們就可以放心的在加載 Custom Elements 的 JavaScript 的 <script> 標簽上使用 async 屬性來延遲加載了(當然,如果是使用 ES6 Modules 形式的話默認的加載行為就會和 defer 類似)。

1246191307-5bc7f1ac6b9fe_articlex.png

Custom Elements + Shadow DOM

使用 Custom Elements 來創(chuàng)建組件時,通常會與 Shadow DOM 進行結合,利用 Shadow DOM 的隔離性,就可以創(chuàng)造獨立的組件。

通常在 Custom Elements 的 constructor() 構造函數(shù)中去創(chuàng)建 Shadow DOM,并對 Shadow DOM 中的節(jié)點添加事件監(jiān)聽、對特定事件觸發(fā)原生 Events 對象。

正常編寫 html 文檔時,我們可能會給 Custom Elements 添加一些子節(jié)點,像這樣:<my-element><h1>Title</h1><p>Content</p></my-element>,而我們創(chuàng)建的 Shadow DOM 又擁有其自己的結構,怎樣將這些子節(jié)點放置到 Shadow DOM 中正確的位置上呢?

在 React 中,這些子節(jié)點被放置在 props 的 children 中,我們可以在 render() 時選擇將它放在哪里。而在 Shadow DOM 中有一個特殊的標簽:<slot>,這個標簽的用處就如同其字面意思,在 Shadow DOM 上放置一個“插槽”,然后 Custom Elements 的子節(jié)點就會自動放置到這個“插槽”中了。

有時我們需要更加精確地控制子節(jié)點在 Shadow DOM 中的位置,而默認情況下,所有子節(jié)點都會被放置在同一個 <slot> 標簽下,即便是你寫了多個 <slot>。那怎樣更精確地對子節(jié)點進行控制呢?

默認情況下,<slot>Fallback</slot> 這樣的是默認的 <slot>,只有第一個默認的 <slot> 會有效,將所有子節(jié)點全部放進去,如果沒有可用的子節(jié)點,將會顯示默認的 Fallback 內容(Fallback 可以是一棵子 DOM 樹)。

<slot> 標簽有一個 name 屬性,當你提供 name 后,它將變?yōu)橐粋“有名字的 <slot>”,這樣的 <slot> 可以存在多個,只要名字各不相同。此時他們會自動匹配 Custom Elements 下帶 slot 屬性并且 slot 屬性與自身 name 相同的子節(jié)點,像這樣

<template id="list">
  <div>
    <h1>Others</h1>
    <slot></slot>
  </div>
  <div>
    <h1>Animals</h1>
    <slot name="animal"></slot>
  </div>
  <div>
    <h1>Fruits</h1>
    <slot name="fruit"></slot>
  </div>
</template>

<my-list>
  <div slot="animal">Cat</div>
  <div slot="fruit">Apple</div>
  <div slot="fruit">Banana</div>
  <div slot="other">flower</div>
  <div>pencil</div>
  <div slot="animal">Dog</div>
  <div slot="fruit">peach</div>
  <div>red</div>
</my-list>
class MyList extends HTMLElement {
  constructor() {
    super();
    const root = this.attachShadow({ mode: 'open' });
    const template = document.getElementById('list');
    root.appendChild(document.importNode(template.content, true));
  }
}
customElements.define('my-list', MyList);

這樣就可以得到如圖所示的結構,#shadow-root (open) 表示這是一個開放的 Shadow DOM,下面的節(jié)點是直接從 template 中 clone 過來的,瀏覽器自動在三個 <slot> 標簽下放置了幾個灰色的 <div> 節(jié)點,實際上這些灰色的 <div> 節(jié)點表示的是到其真實節(jié)點的“引用”,鼠標移動到他們上會顯示一個 reveal 鏈接,點擊這個鏈接即可跳轉至其真實節(jié)點。

1523399701-5bc7f1ce6b75a_articlex.png

這里我們可以看到,雖然 <my-list> 下的子節(jié)點是亂序放置的,但是只要是給定了 slot 屬性,就會被放置到正確的 <slot> 標簽下。注意觀察其中有一個 <div slot="other">flower</div>,這個節(jié)點由于指定了 slot="other",但是卻找不到匹配的 <slot> 標簽,所以它不會被顯示在結果中。

在為 Custom Elements 下的 Shadow DOM 設置樣式的時候,我們可以直接在 Shadow DOM 下放置 <style> 標簽,也可以放置 <link rel="stylesheet">,Shadow DOM 下的樣式都是局部的,所以不用擔心會影響到 Shadow DOM 的外部。并且由于這些樣式僅影響局部,所以對性能也有很大的提升。

在 Shadow DOM 內部的樣式中,也有一些特定的選擇器,比如 :host 選擇器,代表著 ShadowRoot,這類似于普通 DOM 中的 :root,并且它可以與其他偽類組合使用,比如當鼠標在組件上時::host(:hover),當組件擁有某個 class 時::host(.awesome),當組件擁有 disabled 屬性時::host([disabled])……但是 :host 是擁有繼承屬性的,所以如果在 Custom Elements 外部定義了某些樣式,將會覆蓋 :host 中的樣式,這樣就可以輕松地實現(xiàn)各式各樣的“主題風格”了。

為了實現(xiàn)自定義主題,我們還可以使用 Shadow DOM 提供的 :host-context() 選擇器,這個選擇器允許檢查 Shadow DOM 的任何祖先節(jié)點是否包含指定選擇器。比如如果在最外層 DOM 的 <html> 或 <body> 上有一個 class:.night,則 Shadow DOM 內就可以使用 :host-context(.night) 來指定一個夜晚的主題。這樣可以實現(xiàn)主題樣式的繼承。

還有一種樣式的定義方式是利用 CSS 變量。我們在 Shadow DOM 中使用變量來指定樣式,比如:background-color: var(--bg-colour, #0F0);,這樣就可以在 Shadow DOM 外面指定 --bg-colour 變量來設置樣式了,如果沒有指定變量,將使用默認的樣式顏色 #0F0。

有時我們需要在 Shadow DOM 內部使用完全自定義的樣式,比如字體樣式、字體大小,如果任由其繼承可能導致布局錯亂,而每次在組件外面指定樣式又略顯麻煩,并且也破壞了組件的封裝性。所以,Shadow DOM 提供了一個 all 屬性,只要指定 :host{ all: initial; } 就可以重置所有繼承的屬性。

Demo

Web Components 的 Demo 在網(wǎng)上已經有很多了,這是我 2 年前初次接觸 ES6 與 Web Components 的時候寫的一個 Demo:https://github.com/jinliming2/Calendar-js,一個日歷,當時還是 v0 的規(guī)范,并且在 Firefox 下還存在會導致 Firefox 崩潰的 Bug(感覺是 Firefox 在實現(xiàn) Shadow DOM 時的 Bug)。目前這個 Demo 已經不能在 Firefox 下運行了,因為 Firefox 已經刪除了 v0 規(guī)范,開始實行 v1 標準了,所以近期我可能會重構一下這個 Demo。

以上就是你知道原生HTML組件是什么嗎?原生HTML組件的介紹的詳細內容,更多請關注php中文網(wǎng)其它相關文章!


網(wǎng)站建設是一個廣義的術語,涵蓋了許多不同的技能和學科中所使用的生產和維護的網(wǎng)站。