æä»¬å¯ä»¥éè¿æè¿°å¸¦æèªå·±çæ¹æ³ã屿§åäºä»¶ççç±»æ¥å建èªå®ä¹ HTML å ç´ ã
å¨ custom elements ï¼èªå®ä¹æ ç¾ï¼å®ä¹å®æä¹åï¼æä»¬å¯ä»¥å°å ¶å HTML çå 建æ ç¾ä¸å使ç¨ã
è¿æ¯ä¸ä»¶å¥½äºï¼å 为è½ç¶ HTML æé常å¤çæ ç¾ï¼ä½ä»ç¶æ¯æç©·å°½çã妿æä»¬éè¦å <easy-tabs>ã<sliding-carousel>ã<beautiful-upload>â¦â¦ è¿æ ·çæ ç¾ï¼å
建æ ç¾å¹¶ä¸è½æ»¡è¶³æä»¬ã
æä»¬å¯ä»¥æä¸è¿°çæ ç¾å®ä¹ä¸ºç¹æ®çç±»ï¼ç¶å使ç¨å®ä»¬ï¼å°±å¥½åå®ä»¬æ¬æ¥å°±æ¯ HTML çä¸é¨å䏿 ·ã
Custom elements æä¸¤ç§ï¼
- Autonomous custom elements ï¼èªä¸»èªå®ä¹æ ç¾ï¼ ââ âå
¨æ°çâ å
ç´ , ç»§æ¿èª
HTMLElementæ½è±¡ç±». - Customized built-in elements ï¼èªå®ä¹å
建å
ç´ ï¼ ââ ç»§æ¿å
建ç HTML å
ç´ ï¼æ¯å¦èªå®ä¹
HTMLButtonElementçã
æä»¬å°ä¼å å建 autonomous å ç´ ï¼ç¶ååå建 customized built-in å ç´ ã
å¨å建 custom elements çæ¶åï¼æä»¬éè¦åè¯æµè§å¨ä¸äºç»èï¼å æ¬ï¼å¦ä½å±ç¤ºå®ï¼ä»¥å卿·»å å ç´ å°é¡µé¢åå°å ¶ä»é¡µé¢ç§»é¤çæ¶åéè¦åä»ä¹ï¼ççã
éè¿å建ä¸ä¸ªå¸¦æå ä¸ªç¹æ®æ¹æ³çç±»ï¼æä»¬å¯ä»¥å®æè¿ä»¶äºãè¿é常容æå®ç°ï¼æä»¬åªéè¦æ·»å å ä¸ªæ¹æ³å°±è¡äºï¼åæ¶è¿äºæ¹æ³é½ä¸æ¯å¿ é¡»çã
ä¸é¢ååºäºè¿å ä¸ªæ¹æ³çæ¦è¿°ï¼
class MyElement extends HTMLElement {
constructor() {
super();
// å
ç´ å¨è¿éå建
}
connectedCallback() {
// å¨å
ç´ è¢«æ·»å å°ææ¡£ä¹åï¼æµè§å¨ä¼è°ç¨è¿ä¸ªæ¹æ³
//ï¼å¦æä¸ä¸ªå
ç´ è¢«å夿·»å å°ææ¡£ï¼ç§»é¤ææ¡£ï¼é£ä¹è¿ä¸ªæ¹æ³ä¼è¢«å¤æ¬¡è°ç¨ï¼
}
disconnectedCallback() {
// å¨å
ç´ ä»ææ¡£ç§»é¤çæ¶åï¼æµè§å¨ä¼è°ç¨è¿ä¸ªæ¹æ³
// ï¼å¦æä¸ä¸ªå
ç´ è¢«å夿·»å å°ææ¡£ï¼ç§»é¤ææ¡£ï¼é£ä¹è¿ä¸ªæ¹æ³ä¼è¢«å¤æ¬¡è°ç¨ï¼
}
static get observedAttributes() {
return [/* 屿§æ°ç»ï¼è¿äºå±æ§çååä¼è¢«çè§ */];
}
attributeChangedCallback(name, oldValue, newValue) {
// å½ä¸é¢æ°ç»ä¸ç屿§åçååçæ¶åï¼è¿ä¸ªæ¹æ³ä¼è¢«è°ç¨
}
adoptedCallback() {
// å¨å
ç´ è¢«ç§»å¨å°æ°çææ¡£çæ¶åï¼è¿ä¸ªæ¹æ³ä¼è¢«è°ç¨
// ï¼document.adoptNode ä¼ç¨å°, é常å°è§ï¼
}
// è¿å¯ä»¥æ·»å æ´å¤çå
ç´ æ¹æ³å屿§
}
å¨ç³æäºä¸é¢å ä¸ªæ¹æ³ä¹åï¼æä»¬éè¦æ³¨åå ç´ ï¼
// 让æµè§å¨ç¥éæä»¬æ°å®ä¹çç±»æ¯ä¸º <my-element> æå¡ç
customElements.define("my-element", MyElement);
ç°å¨å½ä»»ä½å¸¦æ <my-element> æ ç¾çå
ç´ è¢«åå»ºçæ¶åï¼ä¸ä¸ª MyElement çå®ä¾ä¹ä¼è¢«å建ï¼å¹¶ä¸å颿å°çæ¹æ³ä¹ä¼è¢«è°ç¨ãæä»¬åæ ·å¯ä»¥ä½¿ç¨ document.createElement('my-element') å¨ JavaScript éå建å
ç´ ã
-Custom element åç§°å¿
é¡»å
æ¬ä¸ä¸ªç横线 -, æ¯å¦ my-element å super-button 齿¯ææçå
ç´ åï¼ä½ myelement 并䏿¯ã
è¿æ¯ä¸ºäºç¡®ä¿ custom element åå 建 HTML å ç´ ä¹é´ä¸ä¼åçå½åå²çªã
ä¾å: âtime-formattedâ
举个ä¾åï¼HTML éé¢å·²ç»æ <time> å
ç´ äºï¼ç¨äºæ¾ç¤ºæ¥æï¼æ¶é´ã使¯è¿ä¸ªæ ç¾æ¬èº«å¹¶ä¸ä¼å¯¹æ¶é´è¿è¡ä»»ä½æ ¼å¼åå¤çã
让æä»¬æ¥å建ä¸ä¸ªå¯ä»¥å±ç¤ºéç¨äºå½åæµè§å¨è¯è¨çæ¶é´æ ¼å¼ç <time-formatted> å
ç´ ï¼
<script>
class TimeFormatted extends HTMLElement { // (1)
connectedCallback() {
let date = new Date(this.getAttribute('datetime') || Date.now());
this.innerHTML = new Intl.DateTimeFormat("default", {
year: this.getAttribute('year') || undefined,
month: this.getAttribute('month') || undefined,
day: this.getAttribute('day') || undefined,
hour: this.getAttribute('hour') || undefined,
minute: this.getAttribute('minute') || undefined,
second: this.getAttribute('second') || undefined,
timeZoneName: this.getAttribute('time-zone-name') || undefined,
}).format(date);
}
}
customElements.define("time-formatted", TimeFormatted); // (2)
</script>
<!-- (3) -->
<time-formatted datetime="2019-12-01"
year="numeric" month="long" day="numeric"
hour="numeric" minute="numeric" second="numeric"
time-zone-name="short"
></time-formatted>
- è¿ä¸ªç±»åªæä¸ä¸ªæ¹æ³
connectedCallback()ââ å¨<time-formatted>å ç´ è¢«æ·»å å°é¡µé¢çæ¶åï¼æµè§å¨ä¼è°ç¨è¿ä¸ªæ¹æ³ï¼æè å½ HTML è§£æå¨æ£æµå°å®çæ¶åï¼ï¼å®ä½¿ç¨äºå å»ºçæ¶é´æ ¼å¼åå·¥å · Intl.DateTimeFormatï¼è¿ä¸ªå·¥å ·å¯ä»¥é常好å°å±ç¤ºæ ¼å¼åä¹åçæ¶é´ï¼å¨åæµè§å¨ä¸å ¼å®¹æ§é½é常好ã - æä»¬éè¦éè¿
customElements.define(tag, class)æ¥æ³¨åè¿ä¸ªæ°å ç´ ã - æ¥ä¸æ¥å¨ä»»ä½å°æ¹æä»¬é½å¯ä»¥ä½¿ç¨è¿ä¸ªæ°å ç´ äºã
妿æµè§å¨å¨ customElements.define ä¹åçä»»ä½å°æ¹è§å°äº <time-formatted> å
ç´ ï¼å¹¶ä¸ä¼æ¥éãä½ä¼æè¿ä¸ªå
ç´ å½ä½æªç¥å
ç´ ï¼å°±åä»»ä½éæ åæ ç¾ä¸æ ·ã
:not(:defined) CSS éæ©å¨å¯ä»¥å¯¹è¿æ ·ãæªå®ä¹ãçå
ç´ å 䏿 ·å¼ã
å½ customElement.define 被è°ç¨çæ¶åï¼å®ä»¬è¢«ãå级ãäºï¼ä¸ä¸ªæ°ç TimeFormatted å
ç´ ä¸ºæ¯ä¸ä¸ªæ ç¾å建äºï¼å¹¶ä¸ connectedCallback 被è°ç¨ãå®ä»¬åæäº :definedã
æä»¬å¯ä»¥éè¿è¿äºæ¹æ³æ¥è·åæ´å¤çèªå®ä¹æ ç¾çä¿¡æ¯ï¼
customElements.get(name)ââ è¿åæå® custom elementnameçç±»ãcustomElements.whenDefined(name)â è¿åä¸ä¸ª promiseï¼å°ä¼å¨è¿ä¸ªå ·æç»å®nameç custom element å为已å®ä¹ç¶æçæ¶å resolveï¼ä¸å¸¦å¼ï¼ã
connectedCallback 䏿¸²æï¼è䏿¯ constructor ä¸å¨ä¸é¢çä¾åä¸ï¼å
ç´ éé¢çå
容æ¯å¨ connectedCallback 䏿¸²æï¼å建ï¼çã
为ä»ä¹ä¸å¨ constructor 䏿¸²æï¼
åå å¾ç®åï¼å¨ constructor 被è°ç¨çæ¶åï¼è¿ä¸ºæ¶è¿æ©ãè½ç¶è¿ä¸ªå
ç´ å®ä¾å·²ç»è¢«å建äºï¼ä½è¿æ²¡ææå
¥é¡µé¢ãå¨è¿ä¸ªé¶æ®µï¼æµè§å¨è¿æ²¡æå¤çï¼å建å
ç´ å±æ§ï¼è°ç¨ getAttribute å°ä¼å¾å° nullãæä»¥æä»¬å¹¶ä¸è½å¨é£é渲æå
ç´ ã
èä¸ï¼å¦æä½ ä»ç»èèï¼è¿æ ·ä½å¯¹äºæ§è½æ´å¥½ ââ æ¨è¿æ¸²æç´å°çæ£éè¦çæ¶åã
å¨å
ç´ è¢«æ·»å å°ææ¡£çæ¶åï¼å®ç connectedCallback æ¹æ³ä¼è¢«è°ç¨ãè¿ä¸ªå
ç´ ä¸ä»
ä»
æ¯è¢«æ·»å 为äºå¦ä¸ä¸ªå
ç´ çåå
ç´ ï¼åæ ·ä¹æä¸ºäºé¡µé¢çä¸é¨åãå æ¤æä»¬å¯ä»¥æå»ºå离ç DOMï¼å建å
ç´ å¹¶ä¸è®©å®ä»¬ä¸ºä¹åç使ç¨åå¤å¥½ãå®ä»¬åªæå¨æå
¥é¡µé¢çæ¶åæä¼çç被渲æã
çè§å±æ§
æä»¬ç®åç <time-formatted> å®ç°ä¸ï¼å¨å
ç´ æ¸²æä»¥åï¼åç»ç屿§ååå¹¶ä¸ä¼å¸¦æ¥ä»»ä½å½±åãè¿å¯¹äº HTML å
ç´ æ¥è¯´æç¹å¥æªãé叏彿们æ¹åä¸ä¸ªå±æ§çæ¶åï¼æ¯å¦ a.hrefï¼æä»¬ä¼é¢æç«å³çå°ååãæä»¬å°ä¼å¨ä¸é¢ä¿®æ£è¿ä¸ç¹ã
为äºçè§è¿äºå±æ§ï¼æä»¬å¯ä»¥å¨ observedAttributes() static getter 䏿ä¾å±æ§å表ãå½è¿äºå±æ§åçååçæ¶åï¼attributeChangedCallback ä¼è¢«è°ç¨ãåºäºæ§è½ä¼åçèèï¼å
¶ä»å±æ§ååçæ¶åå¹¶ä¸ä¼è§¦åè¿ä¸ªåè°æ¹æ³ã
以䏿¯ <time-formatted> çæ°çæ¬ï¼å®ä¼å¨å±æ§ååçæ¶åèªå¨æ´æ°ï¼
<script>
class TimeFormatted extends HTMLElement {
render() { // (1)
let date = new Date(this.getAttribute('datetime') || Date.now());
this.innerHTML = new Intl.DateTimeFormat("default", {
year: this.getAttribute('year') || undefined,
month: this.getAttribute('month') || undefined,
day: this.getAttribute('day') || undefined,
hour: this.getAttribute('hour') || undefined,
minute: this.getAttribute('minute') || undefined,
second: this.getAttribute('second') || undefined,
timeZoneName: this.getAttribute('time-zone-name') || undefined,
}).format(date);
}
connectedCallback() { // (2)
if (!this.rendered) {
this.render();
this.rendered = true;
}
}
static get observedAttributes() { // (3)
return ['datetime', 'year', 'month', 'day', 'hour', 'minute', 'second', 'time-zone-name'];
}
attributeChangedCallback(name, oldValue, newValue) { // (4)
this.render();
}
}
customElements.define("time-formatted", TimeFormatted);
</script>
<time-formatted id="elem" hour="numeric" minute="numeric" second="numeric"></time-formatted>
<script>
setInterval(() => elem.setAttribute('datetime', new Date()), 1000); // (5)
</script>
- 渲æé»è¾è¢«ç§»å¨å°äº
render()è¿ä¸ªè¾ 婿¹æ³éé¢ã - è¿ä¸ªæ¹æ³å¨å ç´ è¢«æå ¥å°é¡µé¢çæ¶åè°ç¨ã
attributeChangedCallbackå¨observedAttributes()éç屿§æ¹åçæ¶å被è°ç¨ã- â¦â¦ ç¶å鿏²æå ç´ ã
- æç»ï¼ä¸ä¸ªè®¡æ¶å¨å°±è¿æ ·è¢«æä»¬è½»æ¾å°å®ç°äºã
渲æé¡ºåº
å¨ HTML è§£æå¨æå»º DOM çæ¶åï¼ä¼æç
§å
å顺åºå¤çå
ç´ ï¼å
å¤çç¶çº§å
ç´ åå¤çåå
ç´ ãä¾å¦ï¼å¦ææä»¬æ <outer><inner></inner></outer>ï¼é£ä¹ <outer> å
ç´ ä¼é¦å
被å建并æ¥å
¥å° DOMï¼ç¶åææ¯ <inner>ã
è¿å¯¹ custom elements 产çäºéè¦å½±åã
æ¯å¦ï¼å¦æä¸ä¸ª custom element æ³è¦å¨ connectedCallback å
è®¿é® innerHTMLï¼å®ä»ä¹ä¹æ¿ä¸å°:
<script>
customElements.define('user-info', class extends HTMLElement {
connectedCallback() {
alert(this.innerHTML); // empty (*)
}
});
</script>
<user-info>John</user-info>
å¦æä½ è¿è¡ä¸é¢ç代ç ï¼alert åºæ¥çå
容æ¯ç©ºçã
è¿æ£æ¯å 为å¨é£ä¸ªé¶æ®µï¼åå
ç´ è¿ä¸åå¨ï¼DOM è¿æ²¡æå®ææå»ºãHTML è§£æå¨å
è¿æ¥ custom element <user-info>ï¼ç¶ååå¤çåå
ç´ ï¼ä½æ¯é£æ¶ååå
ç´ è¿å¹¶æ²¡æå è½½ä¸ã
妿æä»¬è¦ç» custom element ä¼ å ¥ä¿¡æ¯ï¼æä»¬å¯ä»¥ä½¿ç¨å ç´ å±æ§ãå®ä»¬æ¯å³æ¶çæçã
æè
ï¼å¦ææä»¬éè¦åå
ç´ ï¼æä»¬å¯ä»¥ä½¿ç¨å»¶è¿æ¶é´ä¸ºé¶ç setTimeout æ¥æ¨è¿è®¿é®åå
ç´ ã
è¿æ ·æ¯å¯è¡çï¼
<script>
customElements.define('user-info', class extends HTMLElement {
connectedCallback() {
setTimeout(() => alert(this.innerHTML)); // John (*)
}
});
</script>
<user-info>John</user-info>
ç°å¨ alert å¨ (*) è¡å±ç¤ºäº ãJohnãï¼å 为æä»¬æ¯å¨ HTML è§£æå®æä¹åï¼æå¼æ¥æ§è¡äºè¿æ®µç¨åºãæä»¬å¨è¿ä¸ªæ¶åå¤çå¿
è¦çåå
ç´ å¹¶ä¸ç»æåå§åè¿ç¨ã
å¦ä¸æ¹é¢ï¼è¿ä¸ªæ¹æ¡å¹¶ä¸æ¯å®ç¾çã妿åµå¥ç custom element åæ ·ä½¿ç¨äº setTimeout æ¥åå§åèªèº«ï¼é£ä¹å®ä»¬ä¼æç
§å
åé¡ºåºæ§è¡ï¼å¤å±ç setTimeout é¦å
触åï¼ç¶åææ¯å
å±çã
è¿æ ·å¤å±å ç´ è¿æ¯æ©äºå å±å ç´ ç»æåå§åã
让æä»¬ç¨ä¸ä¸ªä¾åæ¥è¯´æï¼
<script>
customElements.define('user-info', class extends HTMLElement {
connectedCallback() {
alert(`${this.id} å·²è¿æ¥ã`);
setTimeout(() => alert(`${this.id} åå§å宿ã`));
}
});
</script>
<user-info id="outer">
<user-info id="inner"></user-info>
</user-info>
è¾åºé¡ºåºï¼
- outer å·²è¿æ¥ã
- inner å·²è¿æ¥ã
- outer åå§å宿ã
- inner åå§å宿ã
æä»¬å¯ä»¥å¾ææ¾å°çå°å¤å±å ç´ å¹¶æ²¡æçå¾ å å±å ç´ ã
并没æä»»ä½å
建çåè°æ¹æ³å¯ä»¥å¨åµå¥å
ç´ æ¸²æå¥½ä¹åéç¥æä»¬ã使们å¯ä»¥èªå·±å®ç°è¿æ ·çåè°ãæ¯å¦ï¼å
å±å
ç´ å¯ä»¥åæ´¾å initialized è¿æ ·çäºä»¶ï¼åæ¶å¤å±çå
ç´ çå¬è¿æ ·çäºä»¶å¹¶ååºååºã
Customized built-in elements
æä»¬å建ç <time-formatted> è¿äºæ°å
ç´ ï¼å¹¶æ²¡æä»»ä½ç¸å
³çè¯ä¹ãæç´¢å¼æå¹¶ä¸ç¥æå®ä»¬çåå¨ï¼åæ¶æ éç¢è®¾å¤ä¹æ æ³å¤çå®ä»¬ã
ä½ä¸è¿°ä¸¤ç¹åæ ·æ¯é常éè¦çãæ¯å¦ï¼æç´¢å¼æä¼å¯¹è¿äºäºæ
æå
´è¶£ï¼æ¯å¦æä»¬ççå±ç¤ºäºæ¶é´ãæè
妿æä»¬å建äºä¸ä¸ªç¹å«çæé®ï¼ä¸ºä»ä¹ä¸å¤ç¨å·²æç <button> åè½å¢ï¼
æä»¬å¯ä»¥éè¿ç»§æ¿å 建å ç´ çç±»æ¥æ©å±åå®å¶å®ä»¬ã
æ¯å¦ï¼æé®æ¯ HTMLButtonElement çå®ä¾ï¼è®©æä»¬å¨è¿ä¸ªåºç¡ä¸å建å
ç´ ã
-
æä»¬ç类继æ¿èª
HTMLButtonElementï¼class HelloButton extends HTMLButtonElement { /* custom element æ¹æ³ */ } -
ç»
customElements.defineæä¾å®ä¹æ ç¾ç第ä¸ä¸ªåæ°ï¼customElements.define('hello-button', HelloButton, {extends: 'button'});è¿ä¸æ¥æ¯å¿ è¦çï¼å 为ä¸åçæ ç¾ä¼å ±äº«åä¸ä¸ªç±»ã
-
æåï¼æå ¥ä¸ä¸ªæ®éç
<button>æ ç¾ï¼ä½æ·»åis="hello-button"å°è¿ä¸ªå ç´ ï¼è¿æ ·å°±å¯ä»¥ä½¿ç¨æä»¬ç custom elementï¼<button is="hello-button">...</button>
ä¸é¢æ¯ä¸ä¸ªå®æ´çä¾åï¼
<script>
// è¿ä¸ªæé®å¨è¢«ç¹å»çæ¶å说 "hello"
class HelloButton extends HTMLButtonElement {
constructor() {
super();
this.addEventListener('click', () => alert("Hello!"));
}
}
customElements.define('hello-button', HelloButton, {extends: 'button'});
</script>
<button is="hello-button">Click me</button>
<button is="hello-button" disabled>Disabled</button>
æä»¬æ°å®ä¹çæé®ç»§æ¿äºå
建æé®ï¼æä»¥å®æ¥æåå
建æé®ç¸åçæ ·å¼åæ åç¹æ§ï¼æ¯å¦ disabled 屿§ã
å¼ç¨åè
- HTML ç°è¡æ åï¼ https://html.spec.whatwg.org/#custom-elementsã
- å ¼å®¹æ§ï¼ https://caniuse.com/#feat=custom-elementsã
æ»ç»
æä¸¤ç§ custom elementï¼
-
âAutonomousâ ââ å ¨æ°çæ ç¾ï¼ç»§æ¿
HTMLElementãå®ä¹æ¹å¼ï¼
class MyElement extends HTMLElement { constructor() { super(); /* ... */ } connectedCallback() { /* ... */ } disconnectedCallback() { /* ... */ } static get observedAttributes() { return [/* ... */]; } attributeChangedCallback(name, oldValue, newValue) { /* ... */ } adoptedCallback() { /* ... */ } } customElements.define('my-element', MyElement); /* <my-element> */ -
âCustomized built-in elementsâ ââ å·²æå ç´ çæ©å±ã
éè¦å¤ä¸ä¸ª
.defineåæ°ï¼åæ¶is="..."å¨ HTML ä¸ï¼class MyButton extends HTMLButtonElement { /*...*/ } customElements.define('my-button', MyElement, {extends: 'button'}); /* <button is="my-button"> */
Custom element å¨åæµè§å¨ä¸çå ¼å®¹æ§å·²ç»é常好äºãEdge æ¯æå°ç¸å¯¹è¾å·®ï¼ä½æ¯æä»¬å¯ä»¥ä½¿ç¨ polyfill https://github.com/webcomponents/webcomponentsjsã
è¯è®º
<code>æ ç¾æå ¥åªæå 个è¯ç代ç ï¼æå ¥å¤è¡ä»£ç å¯ä»¥ä½¿ç¨<pre>æ ç¾ï¼å¯¹äºè¶ è¿ 10 è¡ç代ç ï¼å»ºè®®ä½ ä½¿ç¨æ²ç®±ï¼plnkrï¼JSBinï¼codepenâ¦ï¼