/ DOM

[Web] Web Components 初探

其實這好像不是新聞,只是因為他還在 Working Draft 階段,目前的支援度也不高,所以資源相對少。然後,@Paul 寫了一個超強模組,詳見:Module 開發 - illuTrans

所以好像也該來筆記一下。

前言

Google 當初在 Polymer 就是主打 Web Components 這個東西的應用。只是因為太新,所以目前能應用的層面還不算多。況且好像 AngularJS 太紅,所以感覺 Polymer 就有點乏人問津的感覺。

這邊有 Google IO 在去年的演講,有興趣的人可以先看看,

回題,今天要聊一下 Web Components 到底是在做什麼事情。參考資訊來自 W3C 的 Web Components 介紹。

Web Components?

通常,我們製作一個網頁元件,就是把 HTML/CSS/JS 寫在你的 html 檔案裡面,而,Web Components 簡單來說,它能夠將你所製作的元件封裝起來,讓你的網頁元件可以被封裝成一包,然後利用自定義的標簽來使用你的元件。當然,由於目前瀏覽器支援程度尚未完全,所以,還是建議你搭配 Chrome 來看這些事情,不過,有些東西請必須打開,在 chrome://flags 當中,

  • Experimental JavaScript
  • Experimental Web Platform Features
  • HTML Imports

這樣,你就能先行體驗一下 Web Components 所帶來的魔術。

Web Components 包含了這些項目,

  • Templates, 就是你可以定義一個樣板備用
  • Decorators, 裝飾樣式,其實主要的意思是,你可以使用 CSS 來表現你的樣板中的元素
  • Custom Elements, 可以使用自定義元件,自定義標籤或是屬性的擴充等等
  • Shadow DOM, 一種封裝過的 DOM,就是一種將界面元件封裝在自己的 DOM Tree 裡面的 DOM(好饒舌
  • Imports, 可以從外部載入打包過的 Web Components(上述的都可以(除了 CSS

Templates

樣板顧名思義,就是把你的 Markup 用 <template> 包起來,舉例來說,

<template id="myTemplate">
    <div class="slide">
        <div class="slogan">
            <h1>Slider</h1>
        </div>
    </div>
</template>

這個 <template> 有一個屬性 content 可用,當你要複製樣板內容時,可以使用這個 content 來進行操作,

var t = document.querySelector("#myTemplate");
var slide = t.content.cloneNode(true);

document.body.appendChild(slide);

Decorators

主要就是使用 CSS 來控制你的元素,他必須要包含一個 <template> 元件,所以正規的寫法會是像這樣,

<decorator id="myDecorator">
    <template>
        <div class="slide">
            <content></content>
            <div class="slogan">
                <content select="slogan"></content>
            </div>
        </div>
    </template>
</decorator>

你會發現其中有 <content> 標籤,這個標籤所代表的意義是,當你定義好相對應的 Decorators,它會將整個元件輸出成你所設定的樣子。而這個 <content> 就會取代你所設定的內容,舉個例子來說,

slider[open] {
    decorator: url(#myDecorator);
}

當然你必須要準備一些內容,

<slider open>
    <ul>
        <li>First slide</li>
        <li>Second slide</li>
        <li>Third slide</li>
    </ul>
    <slogan>My Slider</slogan>
</slider>

不過很抱歉,因為這個東西實在太新,瀏覽器目前實作不出來(我照著 W3C 的內容改也沒用,假如你是用 W3C 所給的例子,

<details open>
  <summary>Timepieces</summary>
  <ul>
    <li>Sundial
    <li>Cuckoo clock
    <li>Wristwatch
  </ul>
</details>

你如果用 Chrome 開的話,瀏覽器自己會直接幫你實作出來,貌似跟 Web Components 無關(翻白眼!所以,這個部分我就不多著墨,等到官方有更多,或是瀏覽器真的能夠實作這個部分的時候,再來討論吧。

Custom Elements

跟 Decorators 頗類似,但是目前是 Web Components 的靈魂之一(另外一個是 Shadow DOM 待會會提。顧名思義就是,我可以自定義元件,然後在元件裡面做許多事情,例如 JS 或是 CSS 也都可以被寫在裡面。

基本定義

如何定義一個 <element> 呢?

<element name="my-button">
</element>

自定義元件可以使用擴充模式,加上屬性 extends 即可,

<element extends="button" name="my-button">
</element>

這個 extends 頗有意思,在定義上來說,我們所謂的自定義元素相對於 HTML 標籤語義來說,應該都是屬於語義無差別元素 (HTMLUnknownElement),而,當你使用 extends 的時候,就是告訴瀏覽器說,我這個自定義元素,他的語義是接近於某一個 HTML 元素的。

換句話說,上述的 my-button 這個自定義元素,我使用 extends 告訴你,我的這個元素與 <button> 是相似的。這個 extends 的用途大概就是這樣。如果你不使用 extends 的話,那麼瀏覽器就會直接以 name 賦予值來定義你這個元素當作是某一種語義,同樣的,也是屬於語義無差別元素。

定義方法與屬性

是,你沒有看錯,這樣的自定義元素可以被用於定義一種方法或是屬性,很類似把元素拿來當做 API 的載體,舉例來說,

<element name="etag">
  <script>
    ({
        debit: function() {
            return 'always';
        }
    })
  </script>
</element>

這樣,我們自定義的 etag 就會有一個方法,叫做 debit 的方法。推測遠通電收應該是都返回 always

生命週期的 Callbacks

在整個自定義元素的生命週期中,有三種 Callbacks 可以使用,

  • readyCallback
  • insertedCallback
  • removedCallback

意思就是 Callbacks 上面的意思(揍飛

剛剛有說過,我們可以在自定義元素中使用方法跟屬性,同樣的,我們也可以在元素中定義這三種 Callbacks 要做什麼樣的事情。有鑒於大家都是強大的前端工程師,就不多做解釋了,反正就是 Javascript 而已嘛(誒

在 HTML 中使用 Custom Elements

有個條件是,你的自定義元素必須由該元素擴展而來。但是,不確定是不是必要條件(官方也沒講。

<!-- 定義 Custom Elements -->
<element extends="button" name="my-button">
</element>

<!-- 使用 Custom Elements -->
<button is="my-button">
</button>

在 Script 中使用 Custom Elements

只要利用 document.register 就可以把自定義元素綁在你的 Javascript 上面。我想大家的 Javascript 都比我強,所以就不多作解釋。

var myButton = document.register('button', 'my-button', { 
    prototype: Object.create(HTMLButtonElement.prototype, {})
});

var b = new myButton();

自定義元素的定義也可以透過 document.createElement 來做到,以下直接給上 W3C 飯粒範例(不解釋

var b = document.createElement('button', 'fancy-button');
alert(b.outerHTML); // will display '<button is="fancy-button"></button>'
var c = document.createElement('tick-tock-clock');
alert(c.outerHTML); // will display '<tick-tock-clock></tick-tock-clock>'

更新元素

可以透過 CSS 的擬似類別 :unresolved 來更新自定義元素的內容。或許你會問這三尛?嗯,這是為了 Web Components 所特別衍生出來的一種擬似類別,在 CSS 正規文件上目前是看不到的。類似的東西還有前陣子剛出現的 Cat & Hat(不解釋

<style>
tick-tock-clock:unresolved {
  content: '??:??';  
}
</style>
<tick-tock-clock></tick-tock-clock> <!-- will show ??:?? -->

擴展自定義元素

就是剛剛的 extends 只是我們現在拿來擴展自定義元素,這麼做有好處嗎?有啊,請自行參透。

Shadow DOM

另外一個重頭戲,這個概念應該是從 Chrome 來的,然後就這樣紅了起來。或許是 Google 的另外一種邪惡事跡?他的概念就是,在一個元素底下,包含了一整個 #document 完整的 DOM Tree,而這個 DOM 就把他稱作 Shadow DOM。

這個 Shadow DOM 搭配 Template, Custom Elements 可以做相當多邪惡事情,不過,因為這種操作在 DevTools 並不容易觀察出來,所以在開發上會有一點難度(或是挫折...

Shadow DOM 有許多種模式,不過這如果要講起來可能又是好幾篇的事情,所以這裡就先不解釋這麼多了。況且目前的文件,也還是屬於 Working Draft 的階段,有興趣的人可以自己去看看。

Shadow DOM

單一插入點

其實,你可以利用 Chrome 的 DevTools 去看一些 <input> 元素,他的狀態就類似這樣。如果以自定義元素來說,就是擁有一個或多個 content 這樣的元件,Shadow DOM 就是從這樣的元素來插入。

重新繪製

我比較喜歡原文直接翻譯,重投影 (Reprojection)單純是因為我很愛投影這兩個字,意思就是呢,我可以利用樣板來重新產生我的原件內容,舉個例子來說,

Shadow DOM Reprojection

請仔細看圖,你會發現左邊,跟右邊的 DevTools,所展示出的 .breaking 的位置不太一樣。這就是重新繪製元素的作用,它可以利用現有的樣板,將你原有存在於 HTML 的內容重新編排過。

備用內容

原文 Fallback Content,意思是,當你的插入點沒有任何資料的時候,可以拿這個 content 來取代。並不是說他可以相容 IE 好嗎!

Multiple Shadow Subtrees

意思就是你可以一直 Shadow DOM 下去,如果你覺得你不會被自己搞死的時候。當你要使用多層的 Shadow DOM 的時候,有些事情是必須要注意的,

  • Shadow DOM 的宿主 (host) 只會有一個
  • 每一個插入點都有順序
  • 從宿主衍生出來的 Shadow DOM Tree 其根 (root) 無法被刪除,或者說,你不應該嘗試去任意刪除根
  • 增加與刪除都有其順序(所以剛剛說不能任意刪除

至於他的工作方式,大抵上可以這樣解釋,

  • 找到元素第一個 Shadow DOM,並且找到插入點(insertion points,<content>
  • 然後再往下找子元素,看是否有 <shadow> 子元素
  • 然後一直循環的去找子元素與 <shadow> 子元素,如果都沒有了,就回到 Shadow DOM Tree 最上層準備渲染

CSS and Shadow DOM

這不太像是一般的 CSS Selector,所以你要對於 Shadow DOM 製作 CSS 樣式的時候,需要用比較特殊的選擇器來套用。例如,

#news::x-ticker {
  /* Your seyle here. */
}

然後,他也能使用 @host:scope 這樣的設定來做一些比較特別的應用。主要還是依據整個 Shadow DOM 的結構來跑。另一個比較特殊的是,它可以使用 var- 前輟,來讓 CSS 可以當成變數使用。

Events in Shadow DOM

大多數的事件都可以使用在 Shadow DOM 裡面,不過要特別留意的是一些元素上會發生的事情,例如 mouseover 這種,撰寫的時候要特別小心,不然你可能會拿到元素移動到自己身上的這種弔詭的事件響應。

就是元素本身監聽 mouseover,然後 Shadow DOM 也有元素監聽 mouseover 的時候。

Imports

<link rem="import" href="templates.html">

不解釋!

小結

其實 Polymer 還是不錯的!真的!