么是Web Components
Web Components 是一套不同的技術(shù),允許您創(chuàng)建可重用的定制元素(它們的功能封裝在您的代碼之外)并且在您的web應(yīng)用中使用它們。
背景
組件:最初的目的是代碼重用,功能相對(duì)單一或者獨(dú)立。在整個(gè)系統(tǒng)的代碼層次上位于最底層,被其他代碼所依賴,所以說(shuō)組件化是縱向分層。
使用方法
1. 創(chuàng)建一個(gè)類或函數(shù)來(lái)指定web組件的功能,推薦請(qǐng)使用 ECMAScript 2015 的類語(yǔ)法。
2. 使用 CustomElementRegistry.define() 方法注冊(cè)自己的新自定義元素 ,并向其傳遞要定義的元素名稱、指定元素功能的類、以及可選的其所繼承自的元素。
3. 如果需要的話,使用Element.attachShadow() 方法將一個(gè)shadow DOM附加到自定義元素上。使用原生的DOM方法向shadow DOM中添加子元素、事件監(jiān)聽器等。
4. 如果需要的話,使用 <template>和<slot> 定義一個(gè)HTML模板。再次使用常規(guī)DOM方法克隆模板并將其附加到shadow DOM中。
5. 最后在頁(yè)面中使用我們的自定義元素,就像使用原生HTML元素一樣
寫一個(gè)Web Components組件
1. 預(yù)期效果:預(yù)期渲染一個(gè)這樣的自定義品牌名片到頁(yè)面上面
只要開發(fā)者在頁(yè)面上調(diào)用了<company-card></company-card>即可渲染頁(yè)面,根據(jù)規(guī)范,自定義元素的名稱必須包含連詞線,用與區(qū)別原生的 HTML 元素
2. 自定義的元素需要使用js類來(lái)創(chuàng)建,頁(yè)面中所有的自定義元素都會(huì)是這個(gè)類的實(shí)例:
然后就可以實(shí)現(xiàn)基礎(chǔ)效果:
然后我們的js直接取出自定義元素上面的屬性賦值給對(duì)應(yīng)的標(biāo)簽即可
3.使用template來(lái)創(chuàng)建元素
然后我的js直接取出自定義元素上面的屬性賦值給對(duì)應(yīng)的標(biāo)簽即可。
4. 加入樣式
組件的樣式需要加入到template組件里面,為每個(gè)組件獨(dú)享的樣式,跟vue思路差不多最終的template可能是這樣的
5. 最終實(shí)現(xiàn)效果
生命周期函數(shù) 在custom element的構(gòu)造函數(shù)中,可以指定多個(gè)不同的回調(diào)函數(shù),它們將會(huì)在元素的不同生命時(shí)期被調(diào)用
不得不承認(rèn),Web Components 標(biāo)準(zhǔn)的提出解決了一些問(wèn)題,必須交由瀏覽器去處理的是 Shadow DOM,在沒有Shadow DOM 的瀏覽器上實(shí)現(xiàn)代碼隔離的方式多多少少有缺陷。個(gè)人我覺得組件化的各個(gè) API 不夠簡(jiǎn)潔易用,依舊有 getElementById 這些的味道,但是交由各個(gè)類庫(kù)去簡(jiǎn)化也可以接受,而 import 功能上沒問(wèn)題,但是加載多個(gè)
組件時(shí)性能問(wèn)題還是值得商榷,標(biāo)準(zhǔn)可能需要在這個(gè)方面提供更多給瀏覽器的指引,例如是否有可能提供一種單一請(qǐng)求加載多個(gè)組件 HTML 的方式等。
在現(xiàn)在的移動(dòng)化趨勢(shì)中,Web Components 不僅僅是 Web 端的問(wèn)題,越來(lái)越多的開發(fā)者期望以 Web 的方式去實(shí)現(xiàn)移動(dòng)應(yīng)用,而多端復(fù)用的實(shí)現(xiàn)漸漸是以組件的形式鋪開,例如 React Native 和 Weex。所以 Web Components 的標(biāo)準(zhǔn)可能會(huì)影響到多端開發(fā) Web 化的一個(gè)模式和發(fā)展。
但是,對(duì)于Web Components的發(fā)展前景還是比較看好,生產(chǎn)環(huán)境下還是觀望一下就好。
絡(luò)技術(shù)的快速發(fā)展,帶來(lái)了層出不窮的新概念和框架,尤其是在前端開發(fā)領(lǐng)域,新技術(shù)的出現(xiàn)如同浪潮般一波接一波,例如 Vue3 和 Vite 的組合。而在這種技術(shù)快速更新的環(huán)境中,Web Components 作為一項(xiàng)已經(jīng)存在一段時(shí)間的技術(shù),為什么如今值得我們抓緊時(shí)間,去深入學(xué)習(xí)和探討呢?
Web Components 是由 W3C 推動(dòng)的標(biāo)準(zhǔn)化技術(shù)。如今,它得到了包括 Chrome、Firefox、Safari 和 Edge 在內(nèi)的主流瀏覽器的廣泛支持。不僅 「Vue3」 的更新就包括了對(duì) Web Components 的原生支持,現(xiàn)在也出現(xiàn)了很多由Web Components封裝的「組件」和「庫(kù)」,尤其是現(xiàn)在「面試」也成為了常問(wèn)的話題,其中更為頻頻出現(xiàn)的是 Shadow DOM。
這項(xiàng)技術(shù)的魅力在于,「它允許開發(fā)者創(chuàng)建自定義、可重用的元素,這些元素可以在任何符合標(biāo)準(zhǔn)的 Web 應(yīng)用中無(wú)縫使用,而不受限于特定的框架(React、Vue)」。如果你還對(duì) Web Components 比較陌生,那么現(xiàn)在是時(shí)候開始了解這項(xiàng)技術(shù)了。
Web Components 是一種瀏覽器原生支持的 Web 組件化技術(shù),它允許開發(fā)者創(chuàng)建可重用的「自定義元素」,并且可以在任何支持 Web Components 的瀏覽器中使用。
image.png
Web Components 包括以下幾個(gè)核心概念:
今天將圍繞這4個(gè)核心概念以及相關(guān)拓展,通過(guò)例子演示重點(diǎn)說(shuō)一下 Web Components 是如何創(chuàng)建可重用的自定義元素的。
Web Components 最大的特性之一就是能將 HTML 封裝成 Custom Elements(自定義元素)。下面我們通過(guò)一個(gè)簡(jiǎn)單的按鈕例子,看下它是怎么實(shí)現(xiàn)的。
首先,我們需要定義一個(gè)自定義元素。這可以通過(guò)使用 customElements.define() 方法來(lái)實(shí)現(xiàn)。在這個(gè)例子中,我們將創(chuàng)建一個(gè)名為 my-button 的自定義元素。
// main.js
class MyButton extends HTMLElement {
constructor() {
super();
const shadowRoot=this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML=`
<style>
button {
background-color: #4CAF50;
border: none;
color: white;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
padding: 10px 24px;
border-radius: 4px;
}
</style>
<button>Click Me!</button>
`;
}
}
customElements.define('my-button', MyButton);
現(xiàn)在我們已經(jīng)定義了一個(gè)名為 my-button 的自定義元素,我們可以在 HTML 文件中直接使用它。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web Components Example</title>
</head>
<body>
<my-button></my-button>
<script src="./main.js"></script>
</body>
</html>
在這個(gè)例子中,我們創(chuàng)建了一個(gè)名為 my-button 的自定義元素,并在 HTML 文件中直接使用它。這個(gè)自定義元素將渲染為一個(gè)綠色的按鈕,上面寫著“Click Me!”。
不止如此,CustomElements還支持自定義元素行為(如添加點(diǎn)擊事件),也就是說(shuō)既能封裝UI樣式,也是封裝UI交互。
const shadowRoot=this.attachShadow({ mode: 'open' });
shadowRoot.querySelector('button').addEventListener('click', ()=> {
alert('按鈕被點(diǎn)擊了!');
});
到這里為止,便實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的 Web Components,詳細(xì)代碼見CustomElements。
Custom Elements 也有一組生命周期回調(diào)方法(到這里是不是感覺 Web Component 就像 Vue、React似得,怎么還有生命周期?),這些方法在元素的不同生命周期階段被調(diào)用。這些生命周期方法允許你在元素的創(chuàng)建、插入文檔、更新和刪除等時(shí)刻執(zhí)行操作。
以下是自定義元素的一些主要生命周期回調(diào)方法:
下面是一個(gè)簡(jiǎn)單的例子,展示了如何在自定義元素中使用這些生命周期方法:
class MyCustomElement extends HTMLElement {
constructor() {
super();
// 初始化操作,例如創(chuàng)建 Shadow DOM
const shadowRoot=this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML='<p>這是一個(gè)自定義元素</p>';
}
connectedCallback() {
// 元素被插入到 DOM 時(shí)執(zhí)行的操作
console.log('Custom element connected to the DOM');
}
disconnectedCallback() {
// 元素從 DOM 中移除時(shí)執(zhí)行的操作
console.log('Custom element disconnected from the DOM');
}
attributeChangedCallback(name, oldValue, newValue) {
// 監(jiān)聽的屬性發(fā)生變化時(shí)執(zhí)行的操作
console.log(`Attribute ${name} changed from ${oldValue} to ${newValue}`);
}
static get observedAttributes() {
// 返回一個(gè)數(shù)組,包含需要監(jiān)聽的屬性
return ['my-attribute'];
}
}
customElements.define('my-custom-element', MyCustomElement);
在 HTML 中使用這個(gè)自定義元素:
<my-custom-element my-attribute="value"></my-custom-element>
當(dāng) my-custom-element 被插入到 DOM 中時(shí),connectedCallback 會(huì)被調(diào)用。如果元素被從 DOM 中移除,disconnectedCallback 會(huì)被調(diào)用。如果元素的 my-attribute 屬性發(fā)生變化,attributeChangedCallback 會(huì)被調(diào)用。
?
「注意」:監(jiān)聽的同時(shí),也記得停止監(jiān)聽。比如說(shuō)你可能需要在元素連接到 DOM 時(shí)開始監(jiān)聽事件,但是在元素?cái)嚅_連接時(shí)停止監(jiān)聽,避免內(nèi)存泄漏。
?
下面我們將繼續(xù)探討 Shadow DOM,它是 Web Components 的核心特性之一。
Shadow DOM 允許開發(fā)者創(chuàng)建一個(gè)封閉的 DOM 子樹,這個(gè)子樹與主文檔的 DOM 分離,這意味著 Shadow DOM 內(nèi)部的樣式和結(jié)構(gòu)不會(huì)受到外部的影響,也不會(huì)影響到外部。
在“Custom Elements(自定義元素)”的例子中,我們已經(jīng)簡(jiǎn)單使用了 Shadow DOM。
「1、使用 innerHTML」
通過(guò)設(shè)置 Shadow DOM 的 innerHTML 屬性,可以直接添加一個(gè)或多個(gè)元素。這種方式適用于從字符串模板快速填充 Shadow DOM。
class MyElementInnerHTML extends HTMLElement {
constructor() {
super();
const shadowRoot=this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML=`
<style>
p { color: black; }
</style>
<p>使用 innerHTML</p>
`;
}
}
customElements.define('my-element-inner', MyElementInnerHTML);
「2、使用 createElement 和 appendChild」
也可以使用 document.createElement 方法創(chuàng)建一個(gè)新元素,然后使用 appendChild 方法將其添加到 Shadow DOM 中。
const wrapper=document.createElement('p');
wrapper.textContent='使用 createElement 和 appendChild';
var style=document.createElement('style');
style.textContent=`
p { color: gray; }
`;
// 引入外部樣式同樣可以使用 appendChild
// const linkElement=document.createElement('link');
// linkElement.setAttribute('rel', 'stylesheet');
// linkElement.setAttribute('href', 'style.css');
class MyElementAppend extends HTMLElement {
constructor() {
super();
const shadowRoot=this.attachShadow({ mode: 'open' });
shadowRoot.appendChild(wrapper);
shadowRoot.appendChild(style);
// shadowRoot.appendChild(linkElement);
}
}
customElements.define('my-element-append', MyElementAppend);
3、template方式
除上面兩種方式外,還可以使用模板元素 (<template>)添加,具體見下方 「“Templates(模版)”」。
其中在自定義元素的構(gòu)造函數(shù)中,我們調(diào)用了 attachShadow() 方法,并傳入了一個(gè)對(duì)象 { mode: 'open' }。這里的 mode 屬性決定了 Shadow DOM 的封裝模式,它有兩個(gè)可能的值:
在這個(gè)例子中,我們創(chuàng)建了一個(gè) Shadow DOM,并向其中添加了一行文字和相關(guān)的樣式。由于 Shadow DOM 的封裝性,這些樣式只會(huì)在 my-element 元素內(nèi)部生效,不會(huì)影響到頁(yè)面上的其他元素(樣式隔離)。
下面我們更詳細(xì)地探討 Shadow DOM 是否允許外部訪問(wèn),的兩種封裝模式:open 和 closed。
「1、Shadow Mode:open 模式」
當(dāng)使用 open 模式創(chuàng)建 Shadow DOM 時(shí),外部腳本可以通過(guò) Element.shadowRoot 屬性訪問(wèn) Shadow DOM 的根節(jié)點(diǎn)。
這意味著你可以從外部查詢、修改 Shadow DOM 內(nèi)部的元素和樣式。下面是一個(gè)使用 open 模式的例子:
class OpenMyElement extends HTMLElement {
constructor() {
super();
// 創(chuàng)建一個(gè) open 模式的 Shadow DOM
const shadowRoot=this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML=`
<style>
p { color: red; }
</style>
<p>這是一個(gè) open 模式的 Shadow DOM</p>
`;
}
}
customElements.define('open-my-element', OpenMyElement);
// 在外部訪問(wèn) Shadow DOM
const element=document.querySelector('open-my-element');
console.log(element.shadowRoot); // 輸出 ShadowRoot 對(duì)象
在這個(gè)例子中,我們創(chuàng)建了一個(gè)自定義元素 open-my-element,它有一個(gè) open 模式的 Shadow DOM。由于模式是 open,我們可以在外部通過(guò) element.shadowRoot 訪問(wèn) Shadow DOM 的根節(jié)點(diǎn),并進(jìn)行進(jìn)一步的操作,比如添加或刪除子元素,修改樣式等。
image.png
「2、Shadow Mode:closed 模式」
當(dāng)使用 closed 模式創(chuàng)建 Shadow DOM 時(shí),外部腳本無(wú)法通過(guò) Element.shadowRoot 屬性訪問(wèn) Shadow DOM 的根節(jié)點(diǎn)。
這意味著 Shadow DOM 內(nèi)部的元素和樣式對(duì)外部是完全隱藏的,無(wú)法從外部直接訪問(wèn)或修改。 下面是一個(gè)使用 closed 模式的例子:
class ClosedMyElement extends HTMLElement {
constructor() {
super();
// 創(chuàng)建一個(gè) closed 模式的 Shadow DOM
const shadowRoot=this.attachShadow({ mode: 'closed' });
shadowRoot.innerHTML=`
<style>
p { color: blue; }
</style>
<p>這是一個(gè) closed 模式的 Shadow DOM</p>
`;
}
}
customElements.define('closed-my-element', ClosedMyElement);
// 在外部嘗試訪問(wèn) Shadow DOM
const element=document.querySelector('closed-my-element');
console.log(element.shadowRoot); // 輸出 null
在這個(gè)例子中,我們創(chuàng)建了一個(gè)自定義元素 closed-mode-element,它有一個(gè) closed 模式的 Shadow DOM。由于模式是 closed,當(dāng)我們嘗試在外部通過(guò) element.shadowRoot 訪問(wèn) Shadow DOM 的根節(jié)點(diǎn)時(shí),將得到 null。
image.png
open 和 closed 模式?jīng)Q定了 Shadow DOM 的封裝程度:
選擇哪種模式取決于你的具體需求。如果你希望組件的內(nèi)部結(jié)構(gòu)和樣式完全對(duì)外部隱藏,使用 closed 模式是更好的選擇。如果你需要從外部訪問(wèn)和修改組件的內(nèi)部結(jié)構(gòu)和樣式,使用 open 模式會(huì)更合適。
完整代碼,詳見ShadowDOM。
其外,Shadow DOM 還支持更高級(jí)的用法,比如可以將 Shadow DOM 分割成多個(gè) Shadow Trees,使用 slots(插槽)來(lái)插入內(nèi)容,以及使用 template(模板)來(lái)定義可重用的 HTML 結(jié)構(gòu)。
Slots 是一種特殊類型的元素,它允許你將內(nèi)容從組件的一個(gè)部分傳遞到另一個(gè)部分,增加了組件的靈活性。它使得 Web Components 自定義元素,更加的靈活。
例如,我們可以修改 my-button 組件,使其允許用戶自定義按鈕文本:
class MyButton extends HTMLElement {
constructor() {
super();
const shadowRoot=this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML=`
<style>
/* ...樣式代碼保持不變... */
</style>
<button>
<slot>Click Me!</slot>
</button>
`;
}
}
customElements.define('my-button', MyButton);
現(xiàn)在,當(dāng)我們?cè)?HTML 中使用 my-button 時(shí),我們可以向其中插入任何內(nèi)容,它會(huì)替換掉 <slot> 標(biāo)簽:
<my-button>Slots Custom Text</my-button>
image.png
在開發(fā)中,我們更多的還會(huì)遇到不同情況下,選擇插入的內(nèi)容,這里就用到了命名插槽,使用起來(lái)非常方便。
class MyButtonName extends HTMLElement {
constructor() {
super();
const shadowRoot=this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML=`
<style>
/* ...樣式代碼保持不變... */
</style>
<button>
<slot name="element-name"></slot>
<slot name="element-age"></slot>
<slot name="element-email"></slot>
</button>
`;
}
}
customElements.define('my-button-name', MyButtonName);
<my-button-name>
<span slot="element-name">element-name</span>
</my-button-name>
<my-button-name>
<span slot="element-age">element-age</span>
</my-button-name>
<my-button-name>
<span slot="element-email">element-email</span>
</my-button-name>
image.png
是不是很方便,很靈活!!具體代碼詳見Web Components Slots。
Templates 允許你定義一個(gè)可以在多個(gè)組件中重用的 HTML 結(jié)構(gòu)。你可以將模板放在 HTML 文件中的任何位置,并通過(guò) JavaScript 動(dòng)態(tài)地實(shí)例化它們:
<my-button></my-button>
<template id="my-button-template">
<style>
/* ...樣式代碼保持不變... */
</style>
<button>
<slot>Click Me!</slot>
</button>
</template>
在 JavaScript 中,你可以這樣使用模板:
class MyButton extends HTMLElement {
constructor() {
super();
const shadowRoot=this.attachShadow({ mode: 'open' });
const template=document.getElementById('my-button-template');
// 使用`cloneNode()` 方法添加了拷貝到 Shadow root 根節(jié)點(diǎn)上。
shadowRoot.appendChild(template.content.cloneNode(true));
}
}
customElements.define('my-button', MyButton);
image.png
這樣,你就可以在不同的組件中重用同一個(gè)模板,從而提高代碼的可維護(hù)性和重用性。具體代碼下詳見Web Components Templates。
Web Components 是一組用于構(gòu)建可復(fù)用組件的技術(shù),包括 Custom Elements, Shadow DOM, HTML Templates 等。這些技術(shù)的出現(xiàn),使得開發(fā)者能夠更好地組織,去開發(fā)復(fù)雜的網(wǎng)頁(yè)應(yīng)用。然而,由于這些技術(shù)相對(duì)較新,不同瀏覽器的支持情況不盡相同,因此兼容性問(wèn)題也是我們需要重點(diǎn)關(guān)注的方向。
Custom Elements
image.png
Shadow DOM
image.png
HTML Templates
image.png
從上面可以看出,現(xiàn)階段市場(chǎng)上大部分的瀏覽器已經(jīng)都原生支持了 Web Components 的規(guī)范標(biāo)準(zhǔn)。「但是如果說(shuō)出現(xiàn)了兼容性問(wèn)題,我們應(yīng)該怎么處理?」
對(duì)于舊版瀏覽器不支持的兼容性情況,可以考慮使用 polyfill 來(lái)實(shí)現(xiàn)兼容性。Polyfills 是一種代碼注入技術(shù),使得瀏覽器可以支持新的標(biāo)準(zhǔn) API。對(duì)于不支持 Web Components 的瀏覽器,我們可以用 Polyfills 讓這些瀏覽器可以支持 Web Components。
這里我們可以用到 webcomponents.js 庫(kù),它可以實(shí)現(xiàn)兼容 Custom Elements、Shadow DOM 和 HTML Templates 標(biāo)準(zhǔn),讓我們?cè)陂_發(fā)時(shí)不必考慮兼容性問(wèn)題。
npm install @webcomponents/webcomponentsjs
<!-- load webcomponents bundle, which includes all the necessary polyfills -->
<script src="node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>
<!-- load the element -->
<script type="module" src="my-element.js"></script>
<!-- use the element -->
<my-element></my-element>
具體配置詳情,見polyfills webcomponents。
相信大家也比較關(guān)心 Web Components 與現(xiàn)有框架(如 React、Vue)相比有哪些優(yōu)勢(shì)?以及各自適用場(chǎng)景?
首先,Web Components 是一組 Web 平臺(tái) API,允許開發(fā)者創(chuàng)建可重用的自定義元素,而無(wú)需依賴于任何特定的框架。與現(xiàn)有的前端框架,Web Components 有以下幾個(gè)優(yōu)勢(shì):
然而,Web Components 也有其局限性,例如:
總的來(lái)說(shuō),「Web Components 提供了一種標(biāo)準(zhǔn)化且框架無(wú)關(guān)的方式來(lái)構(gòu)建組件,適合組件庫(kù)的開發(fā)」。而框架如 「React、Vue 則在生態(tài)系統(tǒng)支持、開發(fā)體驗(yàn)和數(shù)據(jù)處理方面有明顯優(yōu)勢(shì),適合快速開發(fā)復(fù)雜的應(yīng)用程序」。
Web Components 是 W3C 推動(dòng)的標(biāo)準(zhǔn)化技術(shù),它通過(guò)自定義元素的方式,允許開發(fā)者在瀏覽器中直接使用。這種技術(shù)通過(guò) Shadow DOM 實(shí)現(xiàn)了組件化 DOM 隔離和樣式隔離,確保了組件的獨(dú)立性和可重用性,這些特性被現(xiàn)有很多借鑒和使用。
希望這篇文章對(duì)你有所幫助!!!歡迎在評(píng)論區(qū),一起討論。
今前端編程中,利用語(yǔ)義化的 HTML 結(jié)合 CSS 來(lái)完一個(gè)組件并不是一件難事,這也意味著無(wú)論在 React、Vue 中都可以插入,不過(guò)它倆不是今天的主角,接下來(lái)我將用一個(gè)例子來(lái)介紹如何封裝一個(gè)完整的原生 HTML 的 Web Components 組件,讓我們開始吧!
首先我們來(lái)了解下 HTML 中的 <details> 元素,它可以用于創(chuàng)建一個(gè)小部件,其中包含僅在小部件處于“打開”狀態(tài)時(shí)才可見的附加信息,<details>元素內(nèi)可以包含的內(nèi)容沒有任何限制。
默認(rèn)情況下,元素創(chuàng)建的小部件<details>處于“關(guān)閉”狀態(tài)(open標(biāo)簽可使其打開)。通過(guò)單擊小部件在“打開”和“關(guān)閉”狀態(tài)之間切換,以顯示或隱藏標(biāo)簽中包含的附加信息,內(nèi)部標(biāo)簽 <summary> 元素則可為該部件提供概要。
一個(gè)簡(jiǎn)單的例子如下:
<details>
<summary> 不能說(shuō)的秘密 </summary>
藏的這么深,可還是被你發(fā)現(xiàn)了
</details>
details {
border: 1px solid #aaa;
border-radius: 4px;
padding: .5em .5em 0;
}
summary {
font-weight: bold;
margin: -.5em -.5em 0;
padding: .5em;
}
details[open] {
padding: .5em;
}
details[open] summary {
border-bottom: 1px solid #aaa;
margin-bottom: .5em;
}
使用語(yǔ)義化 HTML 的優(yōu)點(diǎn):頁(yè)面內(nèi)容結(jié)構(gòu)更清晰,方便開發(fā)者閱讀,更利于瀏覽器的理解與加載,搜索引擎解析與SEO優(yōu)化。
原生元素默認(rèn)的樣式很簡(jiǎn)陋,因此我們需要為其定制一下樣式,這塊內(nèi)容我們簡(jiǎn)單帶過(guò),只講解關(guān)鍵部分,樣式內(nèi)容有省略,具體可以在文末的碼上掘金中看到呈現(xiàn)效果。
.ContentWarning > summary {
position: relative;
list-style: none; /** 去除默認(rèn)樣式 **/
user-select: none;
cursor: pointer;
/** 為其添加一個(gè)斜線背景 **/
--stripe-color: rgb(0 0 0 / 0.1);
background-image: repeating-linear-gradient(45deg,
transparent,
transparent 0.5em,
var(--stripe-color) 0.5em,
var(--stripe-color) 1em);
}
/** 通過(guò)var變量調(diào)整懸停時(shí)的顏色樣式 **/
.ContentWarning>summary: hover,
.ContentWarning>summary: focus {
--stripe-color: rgb(150 0 0 / 0.1);
}
現(xiàn)在我們來(lái)把它封裝成一個(gè)完整的組件,這需要先將 HTML 編寫在模板 template 當(dāng)中,并設(shè)置一個(gè) id,如下所示:
<template id="warning-card">
<details class="ContentWarning">
<summary>
<strong>?? 注意:</strong> 以下為隱藏內(nèi)容
</summary>
<slot name="desc"> 藏的這么深,可還是被你發(fā)現(xiàn)了 </slot>
</details>
</template>
熟悉 Vue 的小伙伴應(yīng)該很容易理解上面的代碼,結(jié)構(gòu)很相似,不過(guò)網(wǎng)頁(yè)不會(huì)直接渲染它包裹的內(nèi)容。此外我們還對(duì)此模板設(shè)置了一個(gè)插槽 slot,后面會(huì)講到它的作用。
有了上面封裝好的模板,我們就需要在 JS 中定義成可用組件來(lái)讓其能夠被使用,調(diào)用 window 下的 customElements.define 方法,第一個(gè)參數(shù)是傳入組件名稱,我們定義組件名為: warning-card ,第二個(gè)參數(shù)傳入一個(gè)繼承了 HTMLElement 的類,在其構(gòu)造方法當(dāng)中獲取并克隆一個(gè)新的 HTML 節(jié)點(diǎn),它會(huì)通過(guò) appendChild 渲染到頁(yè)面當(dāng)中。
window.customElements.define('warning-card',
class extends HTMLElement {
constructor() {
super();
var templateElem=document.getElementById('warning-card');
var content=templateElem.content.cloneNode(true);
this.appendChild(content);
}
})
接著我們就可以在頁(yè)面中把它當(dāng)作組件那樣使用了:
<warning-card> </warning-card>
回頭看看上面我們模板中設(shè)置的插槽 slot,此時(shí)還是沒有生效的,我們需要稍微改寫一下構(gòu)造函數(shù)中的渲染方式,將 web 組件定義為一個(gè) Shadow DOM,這樣構(gòu)造的是一個(gè)可以將標(biāo)記結(jié)構(gòu)、樣式和行為隱藏起來(lái),并與頁(yè)面上的其他代碼相隔離,保證不同的部分不會(huì)混在一起的獨(dú)立元素,并在最后使用 Node.cloneNode() 方法添加了模板的拷貝到 Shadow 的根結(jié)點(diǎn)上。
window.customElements.define('warning-card',
class extends HTMLElement {
constructor() {
super();
var template=document.getElementById('warning-card').content;
this.attachShadow({ mode: 'open' }).appendChild(template.cloneNode(true));
}
})
現(xiàn)在我們嘗試使用下組件,往其內(nèi)容添加一個(gè)圖片,指向名為 desc 的 slot 插槽中:
<warning-card>
<img slot="desc" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ba825ffee78c4a1b9c0232e5d2f1d048~tplv-k3u1fbpfcp-watermark.image?" />
</warning-card>
這時(shí)你會(huì)發(fā)現(xiàn),圖片插入到 details 元素的隱藏區(qū)域當(dāng)中了,slot 已經(jīng)成功生效,但是樣式卻消失了,這時(shí)因?yàn)榻M件已經(jīng)被完全隔離,我們需要將樣式作用在其內(nèi)部才會(huì)生效。
<template id="warning-card">
<style>
<!-- TODO: 組件的樣式 -->
</style>
<details class="ContentWarning">
<summary>
<strong>?? 注意:</strong>
</summary>
<slot name="desc">THE DESCRIPTION</slot>
</details>
</template>
這樣組件就正常了:
除了定制模板中的插槽,我們也可以通過(guò) HTML 標(biāo)簽屬性來(lái)實(shí)現(xiàn)一些簡(jiǎn)單的傳參,例如在 summary 標(biāo)簽中顯示一個(gè)標(biāo)題:
<warning-card title="前方高能">
</warning-card>
我們只需要在模板中定義好這個(gè)標(biāo)題的位置:
<template id="warning-card">
<details class="ContentWarning">
<summary>
<!-- TODO: 模板中加入一個(gè)span標(biāo)簽 -->
<strong>?? 注意:</strong> <span id="title"></span>
</summary>
</details>
</template>
最后在構(gòu)造函數(shù)中我們通過(guò) document 的原生方法寫入模板中就可以了:
window.customElements.define('warning-card',
class extends HTMLElement {
constructor() {
super();
var template=document.getElementById('warning-card').content;
// TODO: 找到title標(biāo)簽,寫入傳入組件的title屬性值
template.querySelector('#title').innerText=this.getAttribute('title');
this.attachShadow({ mode: 'open' }).appendChild(template.cloneNode(true));
}
})
至此,我們通過(guò)一個(gè)簡(jiǎn)單的原生組件學(xué)習(xí)了如何編寫 Web Components,可以在此代碼片段中查看具體源碼:原生Web Components組件 - 碼上掘金原生Web Components組件 - 碼上掘金。
以上就是文章的全部?jī)?nèi)容,希望對(duì)你有所幫助!如果覺得文章寫的不錯(cuò),可以點(diǎn)贊收藏,也歡迎關(guān)注,我會(huì)持續(xù)更新更多前端有用的知識(shí)與實(shí)用技巧,我是茶無(wú)味de一天,希望與你共同成長(zhǎng)~
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。