深度介紹:? 你聽說過原生 HTML 元件嗎?

創宇前端發表於2018-10-18

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

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

隨著需求的增長,HTML 的結構越來越複雜,大量重複的程式碼使得頁面改動起來異常困難,這也就孵化了一批批模版工具,將公共的部分抽取出來變為公共元件。再後來,隨著 JavaScript 的效能提升,JavaScript 的地位越來越高,不再只是配菜了,前端渲染的出現降低了服務端解析模版的壓力,服務端只要提供靜態檔案和 API 介面就行了嘛。再然後,前端渲染工具又被搬回了服務端,後端渲染出現了(黑人問號???)

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

那麼,你有聽說過原生 HTML 元件嗎?

四大 Web 元件標準

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

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

Shadow DOM 則是原生元件封裝的基本工具,它可以實現元件與元件之間的獨立性。

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

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

深度介紹:? 你聽說過原生 HTML 元件嗎?

Shadow DOM

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

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

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

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

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

利用 Shadow DOM 的隔離性,我們就可以創造原生的 HTML 元件了。

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

我們來看一看瀏覽器利用 Shadow DOM 實現的一個示例吧,那就是 video 標籤:

<video controls src="./video.mp4" width="400" height="300"></video>
複製程式碼

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

深度介紹:? 你聽說過原生 HTML 元件嗎?

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

在 Chrome 中,Elements 預設是不顯示內部實現的 Shadow DOM 節點的,需要在設定中啟用:

深度介紹:? 你聽說過原生 HTML 元件嗎?

深度介紹:? 你聽說過原生 HTML 元件嗎?

注:瀏覽器預設隱藏自身的 Shadow DOM 實現,但如果是使用者通過指令碼創造的 Shadow DOM,是不會被隱藏的。

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

深度介紹:? 你聽說過原生 HTML 元件嗎?

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

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

video::-webkit-media-controls-overlay-play-button {
  background-color: orange;
}
複製程式碼

深度介紹:? 你聽說過原生 HTML 元件嗎?

由於 Shadow DOM 實際上也是 DOM 的一種,所以在 Shadow DOM 中還可以繼續巢狀 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 可能會與其他元件衝突,你只要確保一個元件內部不衝突就好——這很容易做到。

這解決了現在絕大多數的元件化框架都面臨的問題:Element 的 class(className) 到底怎麼寫?用字首名稱空間的形式會導致 class 名太長,像這樣:.header-nav-list-sublist-button-icon;而使用一些 CSS-in-JS 工具,可以創造一些唯一的 class 名稱,像這樣:.Nav__welcomeWrapper___lKXTg,這樣的名稱仍舊有點長,還帶了冗餘資訊。

ShadowRoot

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

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

對於一個普通的 Element,比如 <div>,你可以通過呼叫它上面的 attachShadow 方法來建立一個 ShadowRoot(還有一個 createShadowRoot 方法,已經過時不推薦使用),attachShadow 接受一個物件進行初始化:{ mode: 'open' },這個物件有一個 mode 屬性,它有兩個取值:'open''closed',這個屬性是在創造 ShadowRoot 的時候需要初始化提供的,並在建立 ShadowRoot 之後成為一個只讀屬性。

mode: 'open'mode: 'closed' 有什麼區別呢?在呼叫 attachShadow 建立 ShadowRoot 之後,attachShdow 方法會返回 ShadowRoot 物件例項,你可以通過這個返回值去構造整個 Shadow DOM。當 mode 為 'open' 時,在用於建立 ShadowRoot 的外部普通節點(比如 <div>)上,會有一個 shadowRoot 屬性,這個屬性也就是創造出來的那個 ShadowRoot,也就是說,在建立 ShadowRoot 之後,還是可以在任何地方通過這個屬性再得到 ShadowRoot,繼續對其進行改造;而當 mode 為 'closed' 時,你將不能再得到這個屬性,這個屬性會被設定為 null,也就是說,你只能在 attachShadow 之後得到 ShadowRoot 物件,用於構造整個 Shadow DOM,一旦你失去對這個物件的引用,你就無法再對 Shadow DOM 進行改造了。

可以從上面 Shadow DOM 的截圖中看到 #shadow-root (user-agent) 的字樣,這就是 ShadowRoot 物件了,而括號中的 user-agent 表示這是瀏覽器內部實現的 Shadow DOM,如果使用通過指令碼自己建立的 ShadowRoot,括號中會顯示為 openclosed 表示 Shadow DOM 的 mode。

深度介紹:? 你聽說過原生 HTML 元件嗎?

瀏覽器內部實現的 user-agent 的 mode 為 closed,所以你不能通過節點的 ShadowRoot 屬性去獲得其 ShadowRoot 物件,也就意味著你不能通過指令碼對這些瀏覽器內部實現的 Shadow DOM 進行改造。

HTML Template

有了 ShadowRoot 物件,我們可以通過程式碼來建立內部結構了,對於簡單的結構,也許我們可以直接通過 document.createElement 來建立,但是稍微複雜一些的結構,如果全部都這樣來建立不僅麻煩,而且程式碼可讀性也很差。當然也可以通過 ES6 提供的反引號字串(const template = `......`;)配合 innerHTML 來構造結構,利用反引號字串中可以任意換行,並且 HTML 對縮排並不敏感的特性來實現模版,但是這樣也是不夠優雅,畢竟程式碼裡大段大段的 HTML 字串並不美觀,即便是單獨抽出一個常量檔案也是一樣。

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

HTML Template 實際上就是在 html 中的一個 <template> 標籤,正常情況下,這個標籤下的內容是不會被渲染的,包括標籤下的 img、style、script 等都是不會被載入或執行的。你可以在指令碼中使用 getElementById 之類的方法得到 <template> 標籤對應的節點,但是卻無法直接訪問到其內部的節點,因為預設他們只是模版,在瀏覽器中表現為 #document-fragment,字面意思就是“文件片段”,可以通過節點物件的 content 屬性來訪問到這個 document-fragment 物件。

深度介紹:? 你聽說過原生 HTML 元件嗎?

通過 document-fragment 物件,就可以訪問到 template 內部的節點了,通過 document.importNode 方法,可以將 document-fragment 物件建立一份副本,然後可以使用一切 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,我們已經可以方便地創造封閉的 Web 元件了,但是目前還有一些不完美的地方:我們必須要在 html 中定義一大批的 <template>,每個元件都要定義一個 <template>

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

通過 HTML Imports,我們可以將 <template> 定義在其他的 html 文件中,然後再在需要的 html 文件中進行匯入(當然也可以通過指令碼按需匯入),匯入後,我們就可以直接使用其中定義的模版節點了。

已經廢棄的 HTML Imports 通過 <link> 標籤實現,只要指定 rel="import" 就可以了,就像這樣:<link rel="import" href="./templates.html">,它可以接受 onloadonerror 事件以指示它已經載入完成。當然也可以通過指令碼來建立 link 節點,然後指定 rel 和 href 來按需載入。Import 成功後,在 link 節點上有一個 import 屬性,這個屬性中儲存的就是 import 進來的 DOM 樹啦,可以 querySelector 之類的,並通過 cloneNodedocument.importNode 方法建立副本後使用。

未來新的 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 樹,這還不是真正的元件。

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

Custom Elements 的核心,實際上就是利用 JavaScript 中的物件繼承,去繼承 HTML 原生的 HTMLElement 類(或是具體的某個原生 Element 類,比如 HTMLButtonElement),然後自己編寫相關的生命週期函式,處理成員屬性以及使用者互動的事件。

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

Custom Elements 的生命週期函式並不多,但是足夠使用。這裡我將 Custom Elements 的生命週期函式與 React 進行一個簡單的對比:

  • constructor(): 建構函式,用於初始化 state、建立 Shadow DOM、監聽事件之類。

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

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

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

    對應 React 中 Mounting 階段的最後一個生命週期:componentDidMount()

    在這個階段,元件已經被插入到 DOM 樹中了,或是其本身就在 html 檔案中寫好在 DOM 樹上了,這個階段一般是進行一些展示相關的初始化,比如載入資料、圖片、音訊或視訊之類並進行展示。

  • attributeChangedCallback(attrName, oldVal, newVal): 元件屬性發生變化,用於更新元件的狀態。

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

    當元件的屬性(React 中的 props)發生變化時觸發這個生命週期,但是並不是所有屬性變化都會觸發,比如元件的 classstyle 之類的屬性發生變化一般是不會產生特殊互動的,如果所有屬性發生變化都觸發這個生命週期的話,會使得效能造成較大的影響。所以 Custom Elements 要求開發者提供一個屬性列表,只有當屬性列表中的屬性發生變化時才會觸發這個生命週期函式。

    這個屬性列表通過元件類上的一個靜態只讀屬性來宣告,在 ES6 Class 中使用一個 getter 函式來實現,只實現 getter 而不實現 setter,getter 返回一個常量,這樣就是隻讀的了。像這樣:

    class AwesomeElement extends HTMLElement {
      static get observedAttributes() {
        return ['awesome'];
      }
    }
    複製程式碼
  • disconnectedCallback(): 元件被從 DOM 樹中移除,用於進行一些清理操作。

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

  • adoptedCallback(): 元件例項從一個文件被移動到另一個文件。

    這個生命週期是原生元件獨有的,React 中沒有類似的生命週期。這個生命週期函式也並不常用到,一般在操作多個 document 的時候會遇到,呼叫 document.adoptNode() 函式轉移節點所屬 document 時會觸發這個生命週期。

在定義了自定義元件後,我們需要將它註冊到 HTML 標籤列表中,通過 window.customElements.define() 函式即可實現,這個函式接受兩個必須引數和一個可選引數。第一個引數是註冊的標籤名,為了避免和 HTML 自身的標籤衝突,Custom Elements 要求使用者自定義的元件名必須至少包含一個短槓 -,並且不能以短槓開頭,比如 my-elementawesome-button 之類都是可以的。第二個引數是註冊的元件的 class,直接將繼承的子類類名傳入即可,當然也可以直接寫一個匿名類:

window.customElements.define('my-element', class extends HTMLElement {
  ...
});
複製程式碼

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

由於 Custom Elements 是通過 JavaScript 來定義的,而一般 js 檔案都是通過 <script> 標籤外聯的,所以 html 文件中的 Custom Elements 在 JavaScript 未執行時是處於一個預設的狀態,瀏覽器預設會將其內容直接顯示出來。為了避免這樣的情況發生,Custom Elements 在被註冊後都會有一個 :defined CSS 偽類而在註冊前沒有,所以我們可以通過 CSS 選擇器在 Custom Elements 註冊前將其隱藏起來,比如:

my-element:not(:defined) {
  display: none;
}
複製程式碼

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

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

深度介紹:? 你聽說過原生 HTML 元件嗎?

Custom Elements + Shadow DOM

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

通常在 Custom Elements 的 constructor() 建構函式中去建立 Shadow DOM,並對 Shadow DOM 中的節點新增事件監聽、對特定事件觸發原生 Events 物件。

正常編寫 html 文件時,我們可能會給 Custom Elements 新增一些子節點,像這樣:<my-element><h1>Title</h1><p>Content</p></my-element>,而我們建立的 Shadow DOM 又擁有其自己的結構,怎樣將這些子節點放置到 Shadow DOM 中正確的位置上呢?

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

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

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

<slot> 標籤有一個 name 屬性,當你提供 name 後,它將變為一個“有名字的 <slot>”,這樣的 <slot> 可以存在多個,只要名字各不相同。此時他們會自動匹配 Custom Elements 下帶 slot 屬性並且 slot 屬性與自身 name 相同的子節點,像這樣:

<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,下面的節點是直接從 template 中 clone 過來的,瀏覽器自動在三個 <slot> 標籤下放置了幾個灰色的 <div> 節點,實際上這些灰色的 <div> 節點表示的是到其真實節點的“引用”,滑鼠移動到他們上會顯示一個 reveal 連結,點選這個連結即可跳轉至其真實節點。

深度介紹:? 你聽說過原生 HTML 元件嗎?

這裡我們可以看到,雖然 <my-list> 下的子節點是亂序放置的,但是隻要是給定了 slot 屬性,就會被放置到正確的 <slot> 標籤下。注意觀察其中有一個 <div slot="other">flower</div>,這個節點由於指定了 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 中的樣式,這樣就可以輕鬆地實現各式各樣的“主題風格”了。

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

還有一種樣式的定義方式是利用 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 在網上已經有很多了,這是我 2 年前初次接觸 ES6 與 Web Components 的時候寫的一個 Demo:github.com/jinliming2/…,一個日曆,當時還是 v0 的規範,並且在 Firefox 下還存在會導致 Firefox 崩潰的 Bug(感覺是 Firefox 在實現 Shadow DOM 時的 Bug)。目前這個 Demo 已經不能在 Firefox 下執行了,因為 Firefox 已經刪除了 v0 規範,開始實行 v1 標準了,所以近期我可能會重構一下這個 Demo。

總結

Web Components 的概念最初是由 Alex RussellFronteers Conference 2011 提出的,這個概念在當時非常的震撼。2013 年,Google 推出了一個叫做“Polymer”的 Web Components 框架以推動 Web Components 的發展。

2014 年,Chrome 釋出了早期版本的 Web Components 元件規範,包括 Custom Elements v0、Shadow DOM v0 和 HTML Imports。但是此時的規範都還是實驗性的,現在已經不推薦使用,並且被 Custom Elements v1、Shadow DOM v1 標準取代,而 HTML Imports 卻沒有標準化,將來會被 ES6 Modules 所取代。

v0 規範將會在 Chrome 70 中被標記棄用警告,並在 2019 年 3 月左右從 Chrome 73 中刪除。

而 v1 標準已經在 Chrome 54+ 和 Safari 10.1+ 中支援,並計劃本月(2018 年 10 月)在 Firefox 中正式支援(此前 Firefox 已經支援,但預設禁用,需要在 about:config 中啟用)。

而 HTML Templates 作為 HTML5 的功能早已被各大瀏覽器接受並支援。

由於 Web Components 涉及到的子項較多,這裡就不給出 Can I Use 的截圖了,讀者可以自行去搜尋“Web Components”檢視相關相容性,或是點選這裡

Can I Use 中包含了 HTML TemplatesHTML ImportsShadow DOM v0Custom Elements v0Shadow DOM v1Custom Elements v1 的相關瀏覽器相容性和註釋,非常詳細。

原生 HTML 元件基於的 Web Components 不是單一的技術,他是由 W3C 定義的一系列瀏覽器標準組成的,通過瀏覽器自身可以理解的方式去構建元件,這將成為未來的前端標準。


文 / jinliming2

一條對新鮮事物充滿了好奇心的鹹魚

編 / 熒聲

作者過往文章:

本文已由作者授權釋出,版權屬於創宇前端。歡迎註明出處轉載本文。本文連結:knownsec-fed.com/2018-10-05-…

想要訂閱更多來自知道創宇開發一線的分享,請搜尋關注我們的微信公眾號:創宇前端(KnownsecFED)。歡迎留言討論,我們會盡可能回覆。

深度介紹:? 你聽說過原生 HTML 元件嗎?

感謝您的閱讀。

相關文章