ç¬èªã®ã¡ã½ãããããããã£ãã¤ãã³ããªã©ãæã¤ãç¬èªã®ã¯ã©ã¹ã§è¨è¿°ãããã«ã¹ã¿ã HTMLè¦ç´ ã使ãããã¨ãã§ãã¾ãã
ä¸åº¦ã«ã¹ã¿ã è¦ç´ ãå®ç¾©ãããã¨ãçµã¿è¾¼ã¿ã®HTMLè¦ç´ ã¨åãããã«ããã使ç¨ã§ãã¾ãã
HTMLã®ç¨®é¡ã¯è±å¯ã§ãããç¡éã§ã¯ãªãã®ã§ãããã¯ç´ æ´ããããã¨ã§ãã<easy-tabs>, <sliding-carousel>, <beautiful-upload> ãªã©ã¯ããã¾ãããä»ã«å¿
è¦ã¨ãªãã¿ã°ã«ã¤ãã¦èãã¦ãã ããã
ç¹å¥ãªã¯ã©ã¹ã§ããããå®ç¾©ãããã以éã¯ãããããããHTMLã®ä¸é¨ã§ãããã®ããã«ä½¿ç¨ãããã¨ãã§ãã¾ãã
Custom element(ã«ã¹ã¿ã è¦ç´ )ã«ã¯2種é¡ããã¾ãã:
- èªå¾åã«ã¹ã¿ã è¦ç´ (Autonomous custom elements) â æ½è±¡çãª
HTMLElementã¯ã©ã¹ãæ¡å¼µãã âãã¹ã¦ãæ°è¦â ã®è¦ç´ ã§ãã - ã«ã¹ã¿ãã¤ãºãããçµã¿è¾¼ã¿è¦ç´ (Customized built-in elements) â ã«ã¹ã¿ãã¤ãºããã
HTMLButtonElementã®ãããªãçµã¿è¾¼ã¿è¦ç´ ãæ¡å¼µããè¦ç´ ã§ãã
æåã«èªå¾åè¦ç´ ã使ãããã®æ¬¡ã«ã«ã¹ã¿ãã¤ãºãããçµã¿è¾¼ã¿è¦ç´ ã使ãã¦ããã¾ãã
ã«ã¹ã¿ã è¦ç´ ãä½ãã«ã¯ããã©ã¦ã¶ã«ããã¤ãã®è©³ç´°ãæããå¿ è¦ãããã¾ã: ã©ã®ããã«è¡¨ç¤ºããããè¦ç´ ããã¼ã¸ä¸ã«è¿½å ããããåé¤ãããã¨ãã®ä½ããããããªã©ã§ãã
ãããã¯ç¹å¥ãªã¡ã½ãããæã¤ã¯ã©ã¹ãä½ããã¨ã§è¡ãã¾ããã¡ã½ããã¯ãããã§ããã¹ã¦ãªãã·ã§ã³ãªã®ã§ç°¡åã§ãã
ããã¯å®å ¨ãªä¸è¦§ã®ã¹ã±ããã§ãã:
class MyElement extends HTMLElement {
constructor() {
super();
// è¦ç´ ã使ããã¾ãã
}
connectedCallback() {
// ãã©ã¦ã¶ã¯è¦ç´ ã document ã«è¿½å ãããæã«ãããå¼ã³ã¾ã
// (è¦ç´ ãç¹°ãè¿ã追å /åé¤ãããå ´åãä½åº¦ãå¼ã°ãã¾ã)
}
disconnectedCallback() {
// ãã©ã¦ã¶ã¯è¦ç´ ã document ããåé¤ãããæã«ãããå¼ã³ã¾ã
// (è¦ç´ ãç¹°ãè¿ã追å /åé¤ãããå ´åãä½åº¦ãå¼ã°ãã¾ã)
}
static get observedAttributes() {
return [/* 夿´ãç£è¦ãã屿§åã®é
å */];
}
attributeChangedCallback(name, oldValue, newValue) {
// ä¸ã§æããããããã®å±æ§ã夿´ãããã¨ãã«å¼ã°ãã¾ã
}
adoptedCallback() {
// è¦ç´ ãæ°ãã document ã«ç§»åãããã¨ãã«å¼ã°ãã¾ã
// (document.adoptNode ã§çºçãã¾ããããã£ãã«ä½¿ããã¾ãã)
}
// ãã®ä»ã®è¦ç´ ã®ã¡ã½ãããããããã£
// ...
}
ãã®å¾ãè¦ç´ ãç»é²ããå¿ è¦ãããã¾ãã:
// <my-element> ãæã
ãä½ã£ãæ°ããªã¯ã©ã¹ã«ãã£ã¦æä¾ããããã¨ããã©ã¦ã¶ã«ç¥ããã¾ãã
customElements.define("my-element", MyElement);
ããã§ãã¿ã° <my-element> ã® HTML è¦ç´ ã«å¯¾ãã¦ã¯ãMyElement ã®ã¤ã³ã¹ã¿ã³ã¹ã使ãããåè¿°ã®ã¡ã½ãããå¼ã³åºããã¾ããJavaScript ã§ document.createElement('my-element') ãããã®ã§ãOKã§ãã
- ãå«ã¾ãªããã°ããã¾ããã«ã¹ã¿ã è¦ç´ ã®ååã«ã¯ãã¤ãã³ - ãå«ãå¿
è¦ãããã¾ããe.g. my-element ã super-button ã¯æå¹ã§ãããmyelement ã¯ã ãã§ãã
ããã¯ãçµã¿è¾¼ã¿è¦ç´ ã¨ã«ã¹ã¿ã 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>
- ãã®ã¯ã©ã¹ã¯ã¡ã½ããã1ã¤ã ãæã£ã¦ãã¾ã(
connectedCallback())ããã©ã¦ã¶ã¯ã<time-formatted>è¦ç´ ããã¼ã¸ã«è¿½å ãããã¨ã(ããã㯠HTML ãã¼ãµã¼ããããæ¤åºããã¨ã)ã«ãããå¼ã³åºãã¾ãããã®ä¸ã§ã¯ããã©ã¦ã¶éã§ååã«ãµãã¼ãããã¦ããçµã¿è¾¼ã¿ã® Intl.DateTimeFormat ãã¼ã¿ãã©ã¼ããã¿ã¼ã使ç¨ãã¦ãã¾ãã customElements.define(tag, class)ã§æ°ããè¦ç´ ãç»é²ãã¾ãã- ãã®å¾ãã©ãã§ãããã使ããã¨ãã§ãã¾ãã
ãã©ã¦ã¶ã customElements.define ã®åã« <time-formatted> ãè¦ã¤ããå ´åãã¨ã©ã¼ã«ã¯ãªãã¾ãããããããè¦ç´ ã¯ã¾ã ç¥ããã¦ããªãã®ã§ã鿍æºã®ã¿ã°ã®ããã«ãªãã¾ãã
ãã®ãããª âæªå®ç¾©ã®â è¦ç´ 㯠CSS ã»ã¬ã¯ã¿ã§ :not(:defined) ã¨ãã¦ã¹ã¿ã¤ã«ãããã¨ãã§ãã¾ãã
customElement.define ãå¼ã³åºãããã¨ããããã㯠âã¢ããã°ã¬ã¼ãâ ããã¾ãã: ããããã«TimeFormatted ã®æ°ããªã¤ã³ã¹ã¿ã³ã¹ã使ãããconnectedCallback ãå¼ã³åºããã¾ããããã㯠:defined ã«ãªãã¾ãã
ã«ã¹ã¿ã è¦ç´ ã«ã¤ãã¦ã®æ å ±ãåå¾ããããã«ã次ã®ãããªã¡ã½ãããããã¾ã:
customElements.get(name)â æå®ãããnameã®ã«ã¹ã¿ã è¦ç´ ã®ã¯ã©ã¹ãè¿ãã¾ããcustomElements.whenDefined(name)â æå®ãããnameã®ã«ã¹ã¿ã è¦ç´ ãå®ç¾©ãããã¨ãã«è§£æ±ºãã(å¤ã¯ãªã) promise ãè¿ãã¾ãã
constructor ã§ã¯ãªããconnectedCallback ã§ã¬ã³ããªã³ã°ãã¾ãä¸ã®ä¾ã§ã¯ãè¦ç´ ã®ã³ã³ãã³ã㯠connectedCallback ã§ã¬ã³ããªã³ã°(使)ããã¦ãã¾ãã
constructor ã§ã¯ãã¡ãªã®ã§ããããï¼
çç±ã¯ç°¡åã§ã: constructor ãå¼ã°ããæç¹ã§ã¯ã¾ã æ©ãããããã§ããè¦ç´ ã®ã¤ã³ã¹ã¿ã³ã¹ã¯ä½æããã¦ãã¾ãããã¾ã追å ããã¦ãã¾ããããã©ã¦ã¶ã¯ãã®æ®µéã§ã¯ã¾ã 屿§ã®å¦çãå²å½ããã¦ãã¾ãã: getAttribute ã®å¼ã³åºã㯠null ãè¿ãã¾ãããã®ãããããã§ã¬ã³ããªã³ã°ãããã¨ã¯ã§ãã¾ããã
ããã«å ãã¦ãããã©ã¼ãã³ã¹ã®ç¹ã§ããæ¬å½ã«å¿ è¦ã¨ãããã¾ã§å¦çãé ãããã®ã§ããè¯ãã§ãã
connectedCallback ã¯è¦ç´ ã document ã«è¿½å ãããã¨ãã«çºçãã¾ããåã¨ãã¦å¥ã®è¦ç´ ã«è¿½å ãããã ããªããå®éã«ãã¼ã¸ã®ä¸é¨ã«ãªã£ãã¨ãã§ãããã®ãããåé¢ãã(detached)DOMãæ§ç¯ããè¦ç´ ã使ãããã¨ã§ä½¿ãããã«ããããæºåãããã¨ãã§ãã¾ãããããã¯ãã¼ã¸ã«è¿½å ãããã¨ãã«ã®ã¿å®éã«ã¬ã³ããªã³ã°ããã¾ãã
屿§ãç£è¦ãã
<time-formatted> ã®ç¾å¨ã®å®è£
ã§ã¯ãè¦ç´ ã®ã¬ã³ããªã³ã°å¾ããã以ä¸å±æ§ã夿´ãã¦ãå½±é¿ãã¾ãããHTML è¦ç´ ã¨ãã¦å°ãå¤ã§ããé常ã¯ãa.href ã®ããã«å±æ§ã夿´ããã¨ã夿´ã¯ããã«è¦ãã¾ãããªã®ã§ãããã®ããã«ä¿®æ£ãã¾ãããã
observedAttributes() ã®éç㪠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ãã«ãã¼ã¡ã½ããã«ç§»åãã¾ããã - è¦ç´ ããã¼ã¸ã«æ¿å ¥ãããã¨ãã«1åã ãå¼ã³åºãã¾ãã
observedAttributes()ã«ãªã¹ãããã屿§ã®å¤æ´ã«å¯¾ããattributeChangedCallbackãããªã¬ã¼ããã¾ãã- â¦è¦ç´ ãåæç»ãã¾ãã
- æå¾ã«ãç°¡åã«ã©ã¤ãã¿ã¤ãã¼ãä½ãäºãã§ãã¾ãã
ã¬ã³ããªã³ã°é
HTML ãã¼ãµã¼ã DOM ãæ§ç¯ããã¨ããè¦ç´ ã¯æ¬¡ã
ã«å¦çããã¾ã(åã®åã«è¦ª)ãE.g. <outer><inner></inner></outer> ã®å ´åãã¾ã <outer> è¦ç´ ã使ãããDOM ã«æ¿å
¥ãããæ¬¡ã« <inner> ã«ãªãã¾ãã
ããã¯ã«ã¹ã¿ã è¦ç´ ã«å¯¾ãã¦éè¦ãªçµæãããããã¾ãã
ä¾ãã°ãã«ã¹ã¿ã è¦ç´ ã connectedCallback ã®ä¸ã§ innerHTML ã«ã¢ã¯ã»ã¹ãããã¨ãã¦ããä½ãå¾ããã¾ããã:
<script>
customElements.define('user-info', class extends HTMLElement {
connectedCallback() {
alert(this.innerHTML); // 空 (*)
}
});
</script>
<user-info>John</user-info>
ãããå®è¡ãã¦ã alert ã¯ç©ºã§ãã
ããã¯ã¾ãã«ããã®æ®µéã§ã¯åãããããDOMãæªå®äºã ããã§ããHTML ãã¼ãµã¼ã¯ã«ã¹ã¿ã è¦ç´ <user-info> ãå¦çãããã®å¾ãã®åãå¦çãã¦ããã¾ãããã¾ã è¡ã£ã¦ãã¾ããã
ã«ã¹ã¿ã è¦ç´ ã«æ å ±ãæ¸¡ãããå ´åã¯ã屿§ã使ç¨ãããã¨ãã§ãã¾ãããããã¯ããã«å©ç¨å¯è½ã§ãã
ãããã¯ãæ¬å½ã«åãå¿
è¦ãªå ´åãé
å»¶ã¼ãã® setTimeout ã使ã£ã¦ã¢ã¯ã»ã¹ãé
ããããã¨ãã§ãã¾ãã
ããã¯åä½ãã¾ã:
<script>
customElements.define('user-info', class extends HTMLElement {
connectedCallback() {
setTimeout(() => alert(this.innerHTML)); // John (*)
}
});
</script>
<user-info>John</user-info>
ããã§ãHTMLã®æ§æè§£æãå®äºããå¾ãéåæã«å®è¡ãããã¨ã«ãªãã®ã§ãè¡ (*) ã® alert ã§ã¯ âJohnâ ã表示ããã¾ããå¿
è¦ã«å¿ãã¦åãå¦çãã¦ãåæåãå®äºãããã¨ãã§ãã¾ãã
䏿¹ããã®è§£æ±ºçãå®ç§ã§ã¯ããã¾ããããã¹ããããã«ã¹ã¿ã è¦ç´ ãèªèº«ã®åæåã« setTimeout ã使ç¨ãã¦ããå ´åããããã¯ãã¥ã¼ã«å
¥ãããã¾ã(åãä½ãã¾ã): å¤å´ã® setTimeout ãæåã«ããªã¬ã¼ããããã®å¾å
é¨ã®ãã®ãããªã¬ã¼ããã¾ãã
ãã®ãããå¤é¨ã®è¦ç´ ã¯å é¨ã®è¦ç´ ã®åæåã®åã«åæåãçµäºãã¾ãã
ä¾ãæãã¦èª¬æãã¾ããã:
<script>
customElements.define('user-info', class extends HTMLElement {
connectedCallback() {
alert(`${this.id} connected.`);
setTimeout(() => alert(`${this.id} initialized.`));
}
});
</script>
<user-info id="outer">
<user-info id="inner"></user-info>
</user-info>
åºåé :
- outer connected.
- inner connected.
- outer initialized.
- inner initialized.
å¤é¨ã®è¦ç´ ãå é¨ã®è¦ç´ ãå¾ ã£ã¦ããªããã¨ãã¯ã£ããåããã¾ãã
ãã¹ãããè¦ç´ ã®æºåãã§ããå¾ã«ããªã¬ã¼ãããçµã¿è¾¼ã¿ã®ã³ã¼ã«ããã¯ã¯ããã¾ãããããããç¬èªã«å®è£
ãããã¨ã¯ã§ãã¾ããä¾ãã°ãå
é¨ã®è¦ç´ 㯠initialized ã®ãããªã¤ãã³ããåµåºããå¤é¨ã®è¦ç´ ã¯ããããªãã¹ã³ãåå¿ããã¨ãã£ãæ¹æ³ãèãããã¾ãã
ã«ã¹ã¿ãã¤ãºãããçµã¿è¾¼ã¿ã®è¦ç´
<time-formatted> ã®ãããªæ°ãã使ããè¦ç´ ã«ã¯ãé¢é£ããã»ãã³ãã£ã¯ã¹ãããã¾ããããããã¯æ¤ç´¢ã¨ã³ã¸ã³ã«ã¨ã£ã¦ã¯æªç¥ã§ãããã¢ã¯ã»ã·ããªãã£ããã¤ã¹ã¯ããããæ±ããã¨ãã§ãã¾ããã
ãããããã®ãããªãã¨ãéè¦ã«ãªããã¨ãããã¾ããE.g. æ¤ç´¢ã¨ã³ã¸ã³ã¯ç§ãã¡ãå®éã«æå»ãè¦ãã¦ããã¨ãããã¨ãç¥ããã¨ã«èå³ãæã¤ããããã¾ãããã¾ãç¹å¥ãªç¨®é¡ã®ãã¿ã³ãä½ã£ã¦ããå ´åãæ¢åã® <button> ã®æ©è½ãåå©ç¨ãããã©ãã§ãããã
ç§ãã¡ã¯ãçµã¿è¾¼ã¿ã®ã¯ã©ã¹ããç¶æ¿ãããã¨ã§ãçµã¿è¾¼ã¿ã®è¦ç´ ãæ¡å¼µãããã«ã¹ã¿ãã¤ãºãããã¨ãã§ãã¾ãã
ä¾ãã°ããã¿ã³ã¯ HTMLButtonElement ã®ã¤ã³ã¹ã¿ã³ã¹ã§ãããããåºã«ä½ãã¾ãããã
-
HTMLButtonElementãæ¡å¼µããã¯ã©ã¹ãä½ãã¾ã:class HelloButton extends HTMLButtonElement { /* ã«ã¹ã¿ã è¦ç´ ã®ã¡ã½ãã */ } -
ã¿ã°ãæå®ãã3çªç®ã®å¼æ°ã
customElements.defineã«æ¸¡ãã¾ã:customElements.define('hello-button', HelloButton, {extends: 'button'});åãã¯ã©ã¹ãå ±æãããã¾ãã¾ãªã¿ã°ãåå¨ããã®ã§ããã®æå®ãå¿ è¦ã«ãªãã¾ãã
-
æå¾ã«ãã«ã¹ã¿ã è¦ç´ ã使ãããã«ãé常ã®
<button>ã¿ã°ãæ¿å ¥ãã¾ãããããã«is="hello-button"ã追å ãã¾ãã:<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 Living Standard: https://html.spec.whatwg.org/#custom-elements.
- äºææ§: https://caniuse.com/#feat=custom-elements.
ãµããª
ã«ã¹ã¿ã è¦ç´ ã«ã¯2ã¤ã®ã¿ã¤ããããã¾ã:
-
âèªå¾åâ â æ°ããã¿ã°ã§
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> */ -
âã«ã¹ã¿ãã¤ãºãããçµã¿è¾¼ã¿è¦ç´ â â æ¢åè¦ç´ ã®æ¡å¼µã§ã
ããï¼ã¤ã®
.defineã®å¼æ°ã¨ãHTML ã«ã¯is="..."ãå¿ è¦ã§ã:class MyButton extends HTMLButtonElement { /*...*/ } customElements.define('my-button', MyElement, {extends: 'button'}); /* <button is="my-button"> */
ã«ã¹ã¿ã è¦ç´ ã¯ãã©ã¦ã¶éã§ã¯ååã«ãµãã¼ãããã¦ãã¾ããEdge ã¯å°ãé ãã¦ãã¾ãããpolyfill https://github.com/webcomponents/webcomponentsjs ãããã¾ãã
ã³ã¡ã³ã
<code>ã¿ã°ã使ã£ã¦ãã ãããè¤æ°è¡ã®å ´åã¯<pre>ãã10è¡ãè¶ ããå ´åã«ã¯ãµã³ãããã¯ã¹ã使ã£ã¦ãã ãã(plnkr, JSBin, codepenâ¦)ã