跳到主要內容
黯羽輕揚每天積累一點點

Web Components

免費2017-12-01#Front-End#Web Components标准化#Web Components入门指南#Web Components spec#Web Components status

Web Components,到底是什麼東西?

一.What?

Web Components are a new browser feature that provides a standard component model for the Web, consisting of several pieces: Shadow DOM, Custom Elements, HTML Imports and HTML Templates.

(摘自 w3c/webcomponents

也就是說,Web Components 是 Web 元件模型標準,由瀏覽器提供原生特性支援,包括 Shadow DOM,Custom Elements,HTML Imports 和 HTML Templates

規範狀態

依賴的 4 個東西,目前(2017/12/1)狀態如下:

Completed Work
2014-03-18  HTML Templates  Group Note

Drafts
2017-09-05  Shadow DOM  Working Draft
2016-10-13  Custom Elements  Working Draft
2016-02-25  HTML Imports  Working Draft

Obsolete
2014-07-24  Introduction to Web Components  Retired

(摘自 All Standards and Drafts - W3C

那麼,這 4 個依賴項現在只完成了 1 個(HTML Templates),而且還沒能成為推薦標準(只是 NOTE 而沒能成為 REC)。其餘 3 個都還處於工作草案(WD)狀態

P.S. 關於 REC,NOTE 等 W3C 文件狀態的更多資訊,請檢視 [W3C 規範制定流程](/articles/w3c 規範制定流程/)

而 Web Components 規範自身在 14 年出過一版(Introduction to Web Components),被廢棄掉了(Retired),目前 Web Components 處於無規範狀態,github 好像 還有動靜

P.S. 實在好奇的話,可以看一眼 被廢棄的版本

實現狀態

雖然規範尚處於不明朗的狀態,但部分瀏覽器對 Web Components 依賴的特性已經提供了不同程度的支援:

Shadow DOM
    v0 Chrome25+ Firefox X Safari X Opera15+ Android4.4+ IOS Safari X
    v1 Chrome53+ Firefox X Safari 10+ Opera40+ Android5+ IOS Safari 10.2+
Custom Elements
    v0 Chrome33+ Firefox X Safari X Opera20+ Android4.4.4+ IOS Safari X
    v1 Chrome54+ Firefox X Safari 10.1+ Opera41+ Android5+ IOS Safari 10.3
HTML Imports
    Chrome36+ Firefox X Safari X Opera23+ Android5+ IOS Safari X
HTML Templates
    Chrome26+ Firefox 22 Safari 7.1 Opera15+ Android4.4+ IOS Safari 8

注意:Android5+ 指的是 Android5-6.x WebView: Chromium 62,而且未區分部分支援與完整支援

Chrome,Opera 一路綠到底,Safari 及移動端差不多跟得上腳步,Firefox 打打醬油。但從目前的相容形勢來看,PC 端和移動端都不可用

P.S. 關於相容性的更多資訊,請檢視 Can I use... Support tables for HTML5, CSS3, etc

此外,還有 polyfill 和元件庫可以試玩:

作用

Web Components 希望提供規範的元件定義方式,推進 Web 元件標準化

由瀏覽器來支援元件化方案需要的環境特性,包括 Shadow DOM,Custom Elements,HTML Imports 和 Templates

那麼先看看這幾個依賴特性能做什麼:

  • Shadow DOM:沙箱環境,用於元件隔離。元件不受外部影響,元件間也互不影響

  • Custom Elements:元件引用方式。以自定義元素的形式引用元件

  • HTML Imports 和 Templates:元件資源載入方式與元件宣告方式。元件宣告放在 <template> 標籤裡,通過 <link rel="import" href> 載入元件資源

我們發現核心是元件封裝,通過 Shadow DOM 把元件細節隱藏起來,效果類似於:

<video src="./video.mp4" controls></video>

含有該片段的 HTML 頁面將呈現一個功能完整的影片播放器,帶播放按鈕,進度條,音量調節按鈕等等

Web Components 的用法與之類似:

<my-app>
    <my-nav-bar>
        <my-title>Order Center</my-title>
    </my-nav-bar>
    <my-aside-menu/>
    <my-filter-panel/>
    <my-order-list/>
</my-app>

把檢視結構和樣式包進自定義 HTML 元素裡,以黑盒元件形式引用。元件定義在隔離的環境中(Shadow DOM),HTML,CSS,Script 都是安全的,外部無法直接改變元件內部的邏輯/檢視狀態

當然,元件除了封裝性,至少還欠缺:

這部分內容應該定義在 Web Components 自身規範裡(比如之前被廢棄掉的那個 Introduction),至於更上層的東西,則不在 Web Components 的考慮範圍內,畢竟元件標準化的第一步應該是從無到有

二。從 video 說起

只要寫一行 video 標籤:

<video src="./foo.webm" controls></video>

頁面就能呈現出功能完整的影片播放器,那播放按鈕,進度條的結構定義和樣式宣告都藏在哪裡呢?難道是像單選按鈕等表單元素一樣,由系統平臺渲染控制元件?這樣的話,那平臺無關的部分呢,比如文字方塊的 placeholder,是怎麼實現的?

實際上,文字方塊的 placeholder 與 video 類似,一些能看到但(在結構化文件裡)找不到的元素都藏在 Shadow DOM 裡:

videoinput 相當於瀏覽器的內建元件,元件檢視結構及預設樣式藏在 Shadow DOM 裡,元件邏輯被徹底藏了起來,僅暴露出 autoplayoninput 等狀態/行為 Hook 與外界通訊

到這裡,我們發現與 Web Components 的概念非常相像,所謂 Web Components 無非是把瀏覽器的元件機制暴露出來,給 Web 開發者用,這樣我們也能愉快地定義「原生控制元件」(元件)了

三.Shadow DOM

前面一直強調把東西藏在 Shadow DOM 裡,因為 Shadow DOM 的作用相當於 sandbox(沙箱),提供元件隔離環境

可以在 Chrome 試玩這個特性:

開啟 Shadow DOM 顯示開關之後,我們瞅一眼文字方塊的隱藏部分:

<input class="nav-search column-07 start-18" name="s" type="text" placeholder="嗯。我看到過你的小熊。」>
  #shadow-root (user-agent)
    <div pseudo="-webkit-input-placeholder" id="placeholder" style="display: block !important; text-overflow: clip;">嗯。我看到過你的小熊。</div>
    <div id="inner-editor"></div>

placeholder 靜靜地待在這裡,#inner-editor 用來顯示輸入的文字

利用瀏覽器提供的 Shadow DOM 特性,我們可以建立自己的 Shadow Root:

document.body.innerHTML = '<div class="container"></div>';
var host = document.querySelector('.container');
var root = host.createShadowRoot();
root.innerHTML = '<p>How <em>you</em> doin?</p>'

此時節點結構是這樣:

<div class="container"></div>
  #shadow-root (open)
    <p>How <em>you</em> doin?</p>

頁面顯示「How you doin?」,因為 Shadow Root 下的內容會在頁面呈現出來

Shadow Root 是指 createShadowRoot() 返回的 Fragment:

host.createShadowRoot() instanceof DocumentFragment === true

可以對 Fragment 做 DOM 操作,相當於一個獨立的 HTML 解析環境,不受外界干擾

另外還有 2 個概念:Shadow Host 與 Shadow Boundary

Shadow Host

Shadow Root 的「宿主」,Shadow DOM 與 DOM 的連線點,說白了就是 Shadow DOM 掛在哪裡(上例中的 host

既然有寄宿關係,那麼我們試一下:

host.parentElement.removeChild(host)

發現 body 裡什麼都沒有了,Shadow DOM 隨著宿主一起被幹掉了(返回值是遊離的 div 節點,此時 Shadow DOM 仍然掛在 div 身上,可以把節點在 append 回來驗證一下)

Shadow Boundary

一個抽象概念,指的是 Shadow DOM 外面的這層「結界」,它能夠把 Shadow Root 下的 HTML 和 CSS 隔離起來,與 Shadow Host 所在的文件裡的樣式互不影響,且外界無法通過 JS 獲取 Shadow Root 下的節點物件,類似於 iframe 的隔離效果

這正是 Web 開發一直想要的模組隔離,雖然可以通過命名空間等工程化方案填補,但總有一些無法彌補的缺陷,根本原因是最終呈現在頁面上的 HTML 與 CSS 沒有作用域的概念,開發階段中的限制手段到這裡都成了道德約束。另一方面,以工程化手段去約束,還可能存在限制太多導致業務束手束腳的問題

Insertion Points

另外,還有必要知道元件組合的基本支援:

<div class="breaking">
    <ul>
        <slot name="breaking"></slot> <!-- slot for breaking news -->
    </ul>
</div>
<div class="other">
    <ul>
        <slot></slot> <!-- slot for the rest of the news -->
    </ul>
</div>

(摘自 Shadow DOM W3C Editor's Draft 14 November 2017

兩種插入方式,分別表示具名槽和預設槽,看著很眼熟,Vue 模版就這麼寫,因為:

For example, Vue components implement the Slot API and the is special attribute.

至於 Vue 與 Web Components 的關係,我們後面再說

四.Custom Elements

建立自定義元素,錦上添花的小東西。沒有自定義元素特性的話,我們需要這樣做:

<ul class="stories">
    <li><a href="//example.com/stories/1">A story</a></li>
    <li><a href="//example.com/stories/2">Another story</a></li>
    <li class="breaking" slot="breaking"><a href="//example.com/stories/3">Also a story</a></li>
    <li><a href="//example.com/stories/4">Yet another story</a></li>
    <li><a href="//example.com/stories/5">Awesome story</a></li>
    <li class="breaking" slot="breaking"><a href="//example.com/stories/6">Horrible story</a></li>
</ul>

有了的話,就這樣寫:

<stories>
    <li><a href="//example.com/stories/1">A story</a></li>
    <li><a href="//example.com/stories/2">Another story</a></li>
    <breaking slot="breaking"><a href="//example.com/stories/3">Also a story</a></breaking>
    <li><a href="//example.com/stories/4">Yet another story</a></li>
    <li><a href="//example.com/stories/5">Awesome story</a></li>
    <breaking slot="breaking"><a href="//example.com/stories/6">Horrible story</a></breaking>
</stories>

語義的角度看,自定義元素的表達力更強一些,也更簡潔

自定義標籤有 2 個約束:

  • 標籤名必須帶有短線

  • 原型必須繼承自 HTMLElement

同樣可以試玩:

document.body.innerHTML = '<template id="tmpl"><p>How <em>you</em> doin?</p><style>em {font-size: 200%;}</style></template><div class="container"></div>';

// Grab our template full of markup and styles
var tmpl = document.querySelector('#tmpl');

// Create a prototype for a new element that extends HTMLElement
var HowYouDoinProto = Object.create(HTMLElement.prototype);

// Setup our Shadow DOM and clone the template
HowYouDoinProto.createdCallback = function() {
var root = this.createShadowRoot();
root.appendChild(document.importNode(tmpl.content, true));
};

// Register our new element
var HowYouDoin = document.registerElement('how-you-doin', {
prototype: HowYouDoinProto
});

document.querySelector('.container').innerHTML = '<how-you-doin/>';

能夠得到:

<div class="container">
    <how-you-doin>
      #shadow-root (open)
        <p>How <em>you</em> doin?</p>
        <style>em {font-size: 200%;}</style>
    </how-you-doin>
</div>

呈現的內容也如我們所願:一個大號的 you

上例中的 document.registerElement 即 Custom Elements 特性。至於 importNode(),則只是一個普通的 DOM API(不是 Custom Elements 的一部分),用來克隆節點,否則模版就是一次性的

至於 HTML Imports,就更不重要了,同樣有 CORS,簡單的元件載入方案,與 ajax 手動載入元件沒太大區別。在 HTTP2.0 時代真正到來之前,生產環境還是不要分多檔案了

P.S. 對 HTML Imports 感興趣的話,可以檢視線上 Demo 與參考資料

五.Vue 與 Web Components

到現在為止,上面提到的所有例子,怎麼看怎麼像 Vue 元件定義,沒錯,因為 Vue 在實現上遵從了部分 Web Components 規範,比如 Shadow DOM 裡的 slot:

You may have noticed that Vue components are very similar to Custom Elements, which are part of the Web Components Spec. That's because Vue's component syntax is loosely modeled after the spec. For example, Vue components implement the Slot API and the is special attribute.

主要有 2 點區別:

  1. The Web Components Spec is still in draft status, and is not natively implemented in every browser. In comparison, Vue components don't require any polyfills and work consistently in all supported browsers (IE9 and above). When needed, Vue components can also be wrapped inside a native custom element.
  1. Vue components provide important features that are not available in plain custom elements, most notably cross-component data flow, custom event communication and build tool integrations.

因為 Web Components 規範尚不成熟,且支援性並不樂觀,不用 polyfill 就無法投入生產,Vue 依靠構建工具跨過了環境相容性問題,不依賴瀏覽器特性支援,但同時也就捨棄了 Shadow DOM 封裝性等 Web Components 核心優勢

另外,Web Components 是相對底層的元件規範,Vue 除了定義元件規範,還提供了元件通訊,資料繫結等上層方案

六。線上 Demo

地址:http://www.ayqy.net/temp/web-components/image-slider.html

P.S. 一個實現很巧妙的純 CSS 帶指示器的 image slider 元件,很有意思,重新整理了筆者對一般相鄰選擇器(E ~ F)的看法

參考資料

評論

暫無評論,快來發表你的看法吧

提交評論