當今前端編程中,利用語義化的 html 結合 css 來完一個組件并不是一件難事,這也意味著無論在 React、Vue 中都可以插入,不過它倆不是今天的主角,接下來我將用一個例子來介紹如何封裝一個完整的原生 HTML 的 Web Components 組件,讓我們開始吧!
HTML結構
首先我們來了解下 HTML 中的 <details> 元素,它可以用于創建一個小部件,其中包含僅在小部件處于“打開”狀態時才可見的附加信息,<details>元素內可以包含的內容沒有任何限制。
默認情況下,元素創建的小部件<details>處于“關閉”狀態(open標簽可使其打開)。通過單擊小部件在“打開”和“關閉”狀態之間切換,以顯示或隱藏標簽中包含的附加信息,內部標簽 <summary> 元素則可為該部件提供概要。
一個簡單的例子如下:
<details>
<summary> 不能說的秘密 </summary>
藏的這么深,可還是被你發現了
</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;
}

使用語義化 HTML 的優點:頁面內容結構更清晰,方便開發者閱讀,更利于瀏覽器的理解與加載,搜索引擎解析與seo優化。
添加億點樣式
原生元素默認的樣式很簡陋,因此我們需要為其定制一下樣式,這塊內容我們簡單帶過,只講解關鍵部分,樣式內容有省略,具體可以在文末的碼上掘金中看到呈現效果。
.ContentWarning > summary {
position: relative;
list-style: none; /** 去除默認樣式 **/
user-select: none;
cursor: pointer;
/** 為其添加一個斜線背景 **/
--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);
}
/** 通過var變量調整懸停時的顏色樣式 **/
.ContentWarning>summary: hover,
.ContentWarning>summary: focus {
--stripe-color: rgb(150 0 0 / 0.1);
}

封裝模板
現在我們來把它封裝成一個完整的組件,這需要先將 HTML 編寫在模板 template 當中,并設置一個 id,如下所示:
<template id="warning-card">
<details class="ContentWarning">
<summary>
<strong>?? 注意:</strong> 以下為隱藏內容
</summary>
<slot name="desc"> 藏的這么深,可還是被你發現了 </slot>
</details>
</template>
熟悉 Vue 的小伙伴應該很容易理解上面的代碼,結構很相似,不過網頁不會直接渲染它包裹的內容。此外我們還對此模板設置了一個插槽 slot,后面會講到它的作用。
定義組件
有了上面封裝好的模板,我們就需要在 JS 中定義成可用組件來讓其能夠被使用,調用 window 下的 customElements.define 方法,第一個參數是傳入組件名稱,我們定義組件名為: warning-card ,第二個參數傳入一個繼承了 HTMLElement 的類,在其構造方法當中獲取并克隆一個新的 HTML 節點,它會通過 AppendChild 渲染到頁面當中。
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);
}
})
接著我們就可以在頁面中把它當作組件那樣使用了:
<warning-card> </warning-card>
插槽與傳參
回頭看看上面我們模板中設置的插槽 slot,此時還是沒有生效的,我們需要稍微改寫一下構造函數中的渲染方式,將 web 組件定義為一個 Shadow DOM,這樣構造的是一個可以將標記結構、樣式和行為隱藏起來,并與頁面上的其他代碼相隔離,保證不同的部分不會混在一起的獨立元素,并在最后使用 Node.cloneNode() 方法添加了模板的拷貝到 Shadow 的根結點上。
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));
}
})
現在我們嘗試使用下組件,往其內容添加一個圖片,指向名為 desc 的 slot 插槽中:
<warning-card>
<img slot="desc" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ba825ffee78c4a1b9c0232e5d2f1d048~tplv-k3u1fbpfcp-watermark.image?" />
</warning-card>
這時你會發現,圖片插入到 details 元素的隱藏區域當中了,slot 已經成功生效,但是樣式卻消失了,這時因為組件已經被完全隔離,我們需要將樣式作用在其內部才會生效。
<template id="warning-card">
<style>
<!-- TODO: 組件的樣式 -->
</style>
<details class="ContentWarning">
<summary>
<strong>?? 注意:</strong>
</summary>
<slot name="desc">THE DESCRIPTION</slot>
</details>
</template>
這樣組件就正常了:

除了定制模板中的插槽,我們也可以通過 HTML 標簽屬性來實現一些簡單的傳參,例如在 summary 標簽中顯示一個標題:
<warning-card title="前方高能">
</warning-card>
我們只需要在模板中定義好這個標題的位置:
<template id="warning-card">
<details class="ContentWarning">
<summary>
<!-- TODO: 模板中加入一個span標簽 -->
<strong>?? 注意:</strong> <span id="title"></span>
</summary>
</details>
</template>
最后在構造函數中我們通過 document 的原生方法寫入模板中就可以了:
window.customElements.define('warning-card',
class extends HTMLElement {
constructor() {
super();
var template = document.getElementById('warning-card').content;
// TODO: 找到title標簽,寫入傳入組件的title屬性值
template.querySelector('#title').innerText = this.getAttribute('title');
this.attachShadow({ mode: 'open' }).appendChild(template.cloneNode(true));
}
})
結束
至此,我們通過一個簡單的原生組件學習了如何編寫 Web Components,可以在此代碼片段中查看具體源碼:原生Web Components組件 - 碼上掘金原生Web Components組件 - 碼上掘金。
以上就是文章的全部內容,希望對你有所幫助!如果覺得文章寫的不錯,可以點贊收藏,也歡迎關注,我會持續更新更多前端有用的知識與實用技巧,我是茶無味de一天,希望與你共同成長~