注意:這篇文章介紹的 API 還沒有徹底標準化,而且仍在變更中,在項目中使用這些實驗性 API 時請務必謹慎。
javascript
如今的 web 嚴重缺少表達能力。你只要瞧一眼「現代」的 web 應用,好比 GMail,就會明白個人意思:css
堆砌 <div>
一點都不現代。然而可悲的是,這就是咱們構建 web 應用的方式。在現有基礎上咱們不該該有更高的追求嗎?html
您還能夠參考如下HTML5/CSS3相關文章:
《使用CSS3開啓GPU硬件加速提高網站動畫渲染性能》
《HTML5 WebSockets 基礎使用教程》
《關於HTML 5 canvas 的基礎教程》
《讓全部IE支持HTML5的解決方案》html5
HTML 爲咱們提供了一個完美的文檔組織工具,然而 HTML 規範定義的元素卻頗有限。java
假如 GMail 的標記不那麼糟糕,而是像下面這樣漂亮,那會怎樣?web
<hangout-module> <hangout-chat from="Paul, Addy"> <hangout-discussion> <hangout-message from="Paul" profile="profile.png" profile="118075919496626375791" datetime="2013-07-17T12:02"> <p>Feelin' this Web Components thing.</p> <p>Heard of it?</p> </hangout-message> </hangout-discussion> </hangout-chat> <hangout-chat>...</hangout-chat> </hangout-module>
查看演示:canvas
真是使人耳目一新!這個應用太合理了,既有意義,又容易理解。最妙的是,它是可維護的,只要查看聲明結構就能夠清楚地知道它的做用。瀏覽器
自定義元素,救救咱們!就期望你了!websocket
自定義元素容許開發者定義新的 HTML 元素類型。該規範只是 Web 組件模塊提供的衆多新 API 中的一個,但它也極可能是最重要的一個。沒有自定義元素帶來的如下特性,Web 組件都不會存在:app
註冊新元素
使用 document.registerElement()
能夠建立一個自定義元素:
document.registerElement()
的第一個參數是元素的標籤名。這個標籤名必須包括一個連字符(-)。所以,諸如 <x-tags>
、<my-element>
和 <my-awesome-app>
都是合法的標籤名,而 <tabs>
和 <foo_bar>
則不是。這個限定使解析器能很容易地區分自定義元素和 HTML 規範定義的元素,同時確保了 HTML 增長新標籤時的向前兼容。
第二個參數是一個(可選的)對象,用於描述該元素的 prototype。在這裏能夠爲元素添加自定義功能(例如:公開屬性和方法)。稍後詳述。
自定義元素默認繼承自 HTMLElement,所以上一個示例等同於:
調用 document.registerElement('x-foo')
向瀏覽器註冊了這個新元素,並返回一個能夠用來建立 <x-foo>
元素實例的構造器。若是你不想使用構造器,也可使用其餘實例化元素的技術。
提示:若是你不但願在 window 全局對象中建立元素構造器,還能夠把它放進命名空間:
假設平淡無奇的原生 <button>
元素不能知足你的需求,你想將其加強爲一個「超級按鈕」,能夠經過建立一個繼承 HTMLButtonElement.prototype
的新元素,來擴展 <button>
元素:
提示:要建立擴展自元素 B 的元素 A,元素 A 必須繼承元素 B 的 prototype。
這類自定義元素被稱爲類型擴展自定義元素。它們以繼承某個特定 HTMLElement 的方式表達了「元素 X 是一個 Y」。
示例:
你有沒有想過爲何 HTML 解析器對非標準標籤不報錯?好比,咱們在頁面中聲明一個 <randomtag>
,一切都很和諧。根據 HTML 規範的表述:
非規範定義的元素必須使用 HTMLUnknownElement
接口。
– HTML 規範
<randomtag>
是非標準的,它會繼承 HTMLUnknownElement
。
對自定義元素來講,狀況就不同了。擁有合法元素名的自定義元素將繼承 HTMLElement。你能夠按 Ctrl+Shift+J(Mac 系統爲 Cmd+Opt+J)打開控制檯,運行下面這段代碼,獲得的結果將是 true:
注意:在不支持 document.registerElement()
的瀏覽器中,<x-tabs>
仍爲 HTMLUnknownElement
。
UNRESOLVED 元素
因爲自定義元素是經過腳本執行 document.registerElement()
註冊的,所以 它們可能在元素定義被註冊到瀏覽器以前就已經聲明或建立過了。例如:你能夠先在頁面中聲明 <x-tabs>
,之後再調用 document.registerElement('x-tabs')
。
在被提高到其定義以前,這些元素被稱爲 unresolved 元素。它們是擁有合法自定義元素名的 HTML 元素,只是尚未註冊成爲自定義元素。
下面這個表格看起來更直觀一些:
類型 | 繼承自 | 示例 |
---|---|---|
unresolved 元素 | HTMLElement |
<x-tabs> 、<my-element> 、<my-awesome-app> |
未知元素 | HTMLUnknownElement |
<tabs> 、<foo_bar> |
把 unresolved 元素想象成尚處於中間狀態,它們都是等待被瀏覽器提高的潛在候選者。瀏覽器說:「你具有一個新元素的所有特徵,我保證會在賦予你定義的時候將你提高爲一個元素」。
咱們建立普通元素用到的一些技術也能夠用於自定義元素。和全部標準定義的元素同樣,自定義元素既能夠在 HTML 中聲明,也能夠經過 JavaScript 在 DOM 中建立。
實例化自定義標籤
聲明元素:
在 JS 中建立 DOM:
使用 new 操做符:
實例化類型擴展元素
實例化類型擴展自定義元素的方法和自定義標籤驚人地類似。
聲明元素:
在 JS 中建立 DOM:
看,這是接收第二個參數爲 is=""
屬性的 document.createElement()
重載版本。
使用 new 操做符:
如今,咱們已經學習瞭如何使用 document.registerElement()
來向瀏覽器註冊一個新標籤。但這還不夠,接下來咱們要向新標籤添加屬性和方法。
添加 JS 屬性和方法
自定義元素最強大的地方在於,你能夠在元素定義中加入屬性和方法,給元素綁定特定的功能。你能夠把它想象成一種給你的元素建立公開 API 的方法。
這裏有一個完整的示例:
構造 prototype 的方法多種多樣,若是你不喜歡上面這種方式,再看一個更簡潔的例子:
以上兩種方式,第一種使用了 ES5 的 Object.defineProperty
,第二種則使用了 get/set。
元素能夠定義特殊的方法,來注入其生存期內關鍵的時間點。這些方法各自有特定的名稱和用途,它們被恰如其分地命名爲生命週期回調:
回調名稱 | 調用時間點 |
---|---|
createdCallback | 建立元素實例 |
attachedCallback | 向文檔插入實例 |
detachedCallback | 從文檔中移除實例 |
attributeChangedCallback(attrName, oldVal, newVal) | 添加,移除,或修改一個屬性 |
全部生命週期回調都是可選的,你能夠只在須要關注的時間點定義它們。例如:假設你有一個很複雜的元素,它會在 createdCallback()
打開一個 IndexedDB
鏈接。在將其從 DOM 移除時,detachedCallback()
會作一些必要的清理工做。注意:不要過於依賴這些生命週期方法(好比用戶直接關閉瀏覽器標籤),僅將其做爲可能的優化點。
另外一個生命週期回調的例子是爲元素設置默認的事件監聽器:
若是你的元素太笨重,是不會有人用它的。生命週期回調能夠幫你大忙!
添加標記
咱們已經建立好 <x-foo>
並添加了 JavaScript API,但它尚未任何內容。不如咱們給點 HTML 讓它渲染?
生命週期回調在這個時候就派上用場了。咱們甚至能夠用 createdCallback()
給一個元素賦予一些默認的 HTML:
實例化這個標籤並在 DevTools 中觀察(右擊,選擇「審查元素」),能夠看到以下結構:
Shadow DOM 自己是一個封裝內容的強大工具,配合使用自定義元素就更神奇了!
Shadow DOM 爲自定義元素提供了:
1.一種隱藏內部實現的方法,從而將用戶與血淋淋的實現細節隔離開。
2.簡單有效的樣式隔離。
從 Shadow DOM 建立元素,跟建立一個渲染基礎標記的元素很是相似,區別在於 createdCallback()
回調:
咱們並無直接設置 <x-foo-shadowdom>
的 innerHTML,而是爲其建立了一個用於填充標記的 Shadow Root。在 DevTools 設置中勾選「Show Shadow DOM」,你就會看到一個能夠展開的 #shadow-root:
這就是 Shadow Root!
HTML 模板是另外一組跟自定義元素完美融合的新 API。
<template>
元素可用於聲明 DOM 片斷。它們能夠被解析並在頁面加載後插入,以及延遲到運行時才進行實例化。模板是聲明自定義元素結構的理想方案。
示例:註冊一個由 <template>
和 Shadow DOM 建立的元素:
短短几行作了不少事情,咱們挨個來看都發生了些什麼:
<x-foo-from-template>
<template>
建立的p {color: orange;}
不會把整個頁面都搞成橙色)真不錯!
和其餘 HTML 標籤同樣,自定義元素也能夠用選擇器定義樣式:
<style> app-panel { display: flex; } [is="x-item"] { transition: opacity 400ms ease-in-out; opacity: 0.3; flex: 1; text-align: center; border-radius: 50%; } [is="x-item"]:hover { opacity: 1.0; background: rgb(255, 0, 255); color: white; } app-panel > [is="x-item"] { padding: 5px; list-style: none; margin: 0 7px; } </style> <app-panel> <li is="x-item">Do</li> <li is="x-item">Re</li> <li is="x-item">Mi</li> </app-panel>
有了 Shadow DOM 場面就熱鬧得多了,它能夠極大加強自定義元素的能力。
Shadow DOM 爲元素增長了樣式封裝的特性。Shadow Root 中定義的樣式不會暴露到宿主外部或對頁面產生影響。對自定義元素來講,元素自己就是宿主。樣式封裝的屬性也使得自定義元素可以爲本身定義默認樣式。
Shadow DOM 的樣式是一個很大的話題!若是你想更多地瞭解它,推薦你閱讀我寫的其餘文章:
使用 :unresolved 僞類避免無樣式內容閃爍(FOUC)
爲了緩解無樣式內容閃爍的影響,自定義元素規範提出了一個新的 CSS 僞類 :unresolved
。在瀏覽器調用你的 createdCallback()
(見生命週期回調方法一節)以前,這個僞類均可以匹配到 unresolved
元素。一旦產生調用,就意味着元素已經完成提高,成爲它被定義的形態,該元素就再也不是一個 unresolved
元素了。
提示:Chrome 29 已經原生支持 CSS :unresolved
僞類。
示例:註冊後漸顯的 <x-foo>
標籤:
請記住 :unresolved
僞類只能用於 unresolved 元素,而不能用於繼承自 HTMLUnkownElement
的元素(見元素如何提高一節)。
<style> /* 給全部 unresolved 元素添加邊框 */ :unresolved { border: 1px dashed red; display: inline-block; } /* unresolved 元素 x-panel 的文本內容爲紅色 */ x-panel:unresolved { color: red; } /* 定義註冊後的 x-panel 文本內容爲綠色 */ x-panel { color: green; display: block; padding: 5px; display: block; } </style> <panel> I'm black because :unresolved doesn't apply to "panel". It's not a valid custom element name. </panel> <x-panel>I'm red because I match x-panel:unresolved.</x-panel>
瞭解更多 :unresolved
僞類的知識,請看 Polymer 文檔《元素樣式指南》。
Chrome 27 和 Firefox 23 都提供了對 document.registerElement()
的支持,不過以後規範又有一些演化。Chrome 31 將是第一個真正支持新規範的版本。
提示:在 Chrome 31 中使用自定義元素,須要開啓 about:flags 中的「實驗性 web 平臺特性(Experimental Web Platform features)」選項。
在瀏覽器支持穩定以前,也有一些很好的兼容方案:
一直關注標準的人都知道曾經有一個 <element>
標籤。它很是好用,你只要像下面這樣就能夠聲明式地註冊一個新元素:
然而很不幸,在它的提高過程、邊界案例,以及末日般的複雜場景中,須要處理大量的時序問題。<element>
所以被迫擱置。2013 年 8 月,Dimitri Glazkov 在 public-webapps 郵件組中宣告移除 <element>
。
值得注意的是,Polymer 實現了以 <polymer-element>
的形式聲明式地註冊元素。這是怎麼作到的?它用的正是 document.registerElement('polymer-element')
以及我在從模板建立元素一節介紹的技術。
自定義元素爲咱們提供了一個工具,經過它咱們能夠擴展 HTML 的詞彙,賦予它新的特性,並把不一樣的 web 平臺鏈接在一塊兒。結合其餘新的基本平臺,如 Shadow DOM 和 <template>
,咱們領略了 web 組件的宏偉藍圖。標記語言將再次變得很時髦!
若是你對使用 web 組件感興趣,建議你看看 Polymer,它已經有了你想要的一切。
英文原文:《Custom Elements: defining new elements in HTML》
轉載原文:http://forcefront.com/2013/custom-elements-defining-new-elements-in-html