Edition for Web Developers — Last Updated 28 March 2025
Support in all current engines.
カスタム要素は、著者にフル機能のDOM要素を独自に構築する方法を提供する。スクリプティングなどで後に追加されたアプリケーション固有の動作を用いて、著者は文書内で非標準要素を常に使用することができるが、そのような要素は歴史的に不適合であり、あまり機能的ではなかった。カスタム要素を定義することで、著者はパーサーに要素を適切に構築する方法、およびそのクラスの要素が変更にどのように反応すべきかを伝えることができる。
カスタム要素は、(カスタム要素の定義のような)低レベルの著者に公開される拡張ポイントの言葉で、(HTMLの要素のような)既存のプラットフォームの機能を説明することによって、"プラットフォームを合理的に説明し"ようとするより大きな活動の一部である。現在、カスタム要素の能力には、機能的にも意味的にも多くの制限があり、HTMLの既存の要素の振る舞いを完全に説明することを妨げているが、我々は時が経つにつれこの隔たりが縮小することを望む。
自律カスタム要素を作成する方法を説明するために、国旗の小さなアイコンのレンダリングをカプセル化するカスタム要素を定義してみよう。私たちの目標は、次のように使用できるようにすることである:
< flag-icon country = "nl" ></ flag-icon >
これを行うには、まずカスタム要素のクラスを宣言し、HTMLElement
を拡張する:
class FlagIcon extends HTMLElement {
constructor() {
super ();
this . _countryCode = null ;
}
static observedAttributes = [ "country" ];
attributeChangedCallback( name, oldValue, newValue) {
// name will always be "country" due to observedAttributes
this . _countryCode = newValue;
this . _updateRendering();
}
connectedCallback() {
this . _updateRendering();
}
get country() {
return this . _countryCode;
}
set country( v) {
this . setAttribute( "country" , v);
}
_updateRendering() {
// Left as an exercise for the reader. But, you'll probably want to
// check this.ownerDocument.defaultView to see if we've been
// inserted into a document with a browsing context, and avoid
// doing any work if not.
}
}
次に、要素を定義するために次のクラスを使用して必要がある:
customElements. define( "flag-icon" , FlagIcon);
この時点で、上記のコードは動作する。flag-icon
タグを見るたびに、パーサーはFlagIcon
クラスの新しいインスタンスを作成し、要素の内部状態を設定し、レンダリングを更新するために使用する(適切な場合)、新しいcountry
属性についてコードに伝える。
DOM APIを使用してflag-icon
要素を作成することもできる:
const flagIcon = document. createElement( "flag-icon" )
flagIcon. country = "jp"
document. body. appendChild( flagIcon)
最後に、カスタム要素コンストラクター自体を使用することもできる。つまり、上記のコードは次のコードと同じである:
const flagIcon = new FlagIcon()
flagIcon. country = "jp"
document. body. appendChild( flagIcon)
trueの値を持つ静的なformAssociated
プロパティを追加すると、自律カスタム要素がフォーム関連カスタム要素になる。ElementInternals
インターフェイスは、フォームコントロール要素に共通の関数およびプロパティを実装するのに役立つ。
class MyCheckbox extends HTMLElement {
static formAssociated = true ;
static observedAttributes = [ 'checked' ];
constructor() {
super ();
this . _internals = this . attachInternals();
this . addEventListener( 'click' , this . _onClick. bind( this ));
}
get form() { return this . _internals. form; }
get name() { return this . getAttribute( 'name' ); }
get type() { return this . localName; }
get checked() { return this . hasAttribute( 'checked' ); }
set checked( flag) { this . toggleAttribute( 'checked' , Boolean( flag)); }
attributeChangedCallback( name, oldValue, newValue) {
// name will always be "checked" due to observedAttributes
this . _internals. setFormValue( this . checked ? 'on' : null );
}
_onClick( event) {
this . checked = ! this . checked;
}
}
customElements. define( 'my-checkbox' , MyCheckbox);
カスタム要素my-checkbox
は、組み込みのフォームに関連付けられた要素のように使うことができる。たとえば、form
またはlabel
の中に置くことでmy-checkbox
要素が関連付けられ、form
を送信することでmy-checkbox
実装で提供されるデータが送信される。
< form action = "..." method = "..." >
< label >< my-checkbox name = "agreed" ></ my-checkbox > I read the agreement.</ label >
< input type = "submit" >
</ form >
ElementInternals
の適切なプロパティを使用することにより、カスタム要素はデフォルトのアクセシビリティセマンティックスを持つことができる。 次のコードは、前のセクションのフォームに関連付けられたチェックボックスを拡張して、アクセシビリティ技術から見たデフォルトのロールおよびチェック状態を適切に設定する:
class MyCheckbox extends HTMLElement {
static formAssociated = true ;
static observedAttributes = [ 'checked' ];
constructor() {
super ();
this . _internals = this . attachInternals();
this . addEventListener( 'click' , this . _onClick. bind( this ));
this . _internals. role = 'checkbox' ;
this . _internals. ariaChecked = 'false' ;
}
get form() { return this . _internals. form; }
get name() { return this . getAttribute( 'name' ); }
get type() { return this . localName; }
get checked() { return this . hasAttribute( 'checked' ); }
set checked( flag) { this . toggleAttribute( 'checked' , Boolean( flag)); }
attributeChangedCallback( name, oldValue, newValue) {
// name will always be "checked" due to observedAttributes
this . _internals. setFormValue( this . checked ? 'on' : null );
this . _internals. ariaChecked = this . checked;
}
_onClick( event) {
this . checked = ! this . checked;
}
}
customElements. define( 'my-checkbox' , MyCheckbox);
組み込み要素の場合と同様に、これらはデフォルトにすぎず、role
およびaria-*
属性を使用するページ著者によって上書きできることに注意する:
<!-- This markup is non-conforming -->
< input type = "checkbox" checked role = "button" aria-checked = "false" >
<!-- This markup is probably not what the custom element author intended -->
< my-checkbox role = "button" checked aria-checked = "false" >
カスタム要素の著者は、アクセシビリティセマンティックスのどの側面が強いネイティヴセマンティックスであるかを説明することが勧められる。つまり、カスタム要素のユーザーによって上書きされるべきではない。この例では、my-checkbo
x要素の著者は、そのroleおよびaria-checked
値は強いネイティヴセマンティックスであるため、上記のようなコードを勧めないと述べている。
カスタマイズされた組み込み要素は、自律カスタム要素とはわずかに異なる定義があり、そして大きく異なる使われ方をする、独自の種類のカスタム要素である。この要素は、新しいカスタム機能でHTMLの既存要素を拡張することで、HTMLの既存要素由来の動作の再利用を可能にするために存在する。残念なことに、HTML要素の既存の動作の多くが自律カスタム要素を純粋に使用して複製できないため、これは重要である。代わりに、カスタマイズされた組み込み要素は、既存の要素にカスタム構築動作、ライフサイクルフック、およびプロトタイプチェーンのインストールを可能にし、既存の要素の上にこれらの機能を本質的に"混在"させる。
カスタマイズされた組み込み要素は、ユーザーエージェントや他のソフトウェアが要素のセマンティックスおよび動作を識別するために要素のローカル名として扱うので、自律カスタム要素とは異なる構文を必要とする。つまり、既存の動作の上に構築するカスタマイズされた組み込み要素の概念は、元のローカル名を保持する拡張要素に決定的に依存する。
この例において、plastic-button
というカスタマイズされた組み込み要素を作成する。これは通常のボタンのように動作するが、クリックするたびにファンシーなアニメーション効果が追加される。今回はHTMLElement
の代わりにHTMLButtonElement
を拡張するが、前と同じようにクラスを定義することから始める:
class PlasticButton extends HTMLButtonElement {
constructor() {
super ();
this . addEventListener( "click" , () => {
// Draw some fancy animation effects!
});
}
}
カスタム要素を定義する場合、extends
オプションも指定する必要がある:
customElements. define( "plastic-button" , PlasticButton, { extends : "button" });
一般に、拡張されている要素の名前は、同じインターフェイスを共有する要素(HTMLQuoteElement
を共有するq
とblockquote
の両者など)を共有するため、拡張する要素インターフェイスを単に調べるだけでは判断することができない。
解析されたHTMLソーステキストからカスタマイズされた組み込み要素を構築するには、button
要素のis
属性を使用する。
< button is = "plastic-button" > Click Me!</ button >
カスタマイズされた組み込み要素を自律カスタム要素として使用しようとするとうまくいかない。つまり、<plastic-button>Click me?</plastic-button>
は特別な動作をしないHTMLElement
を作成するだけである。
プログラムでカスタマイズしたビルトイン要素を作成する必要がある場合、次のcreateElement()
の形式を使用することができる:
const plasticButton = document. createElement( "button" , { is: "plastic-button" });
plasticButton. textContent = "Click me!" ;
前と同じように、コンストラクターも動作する:
const plasticButton2 = new PlasticButton();
console. log( plasticButton2. localName); // will output "button"
console. assert( plasticButton2 instanceof PlasticButton);
console. assert( plasticButton2 instanceof HTMLButtonElement);
プログラムでカスタマイズした組み込み要素を作成する場合、明示的に設定されていないのでis
属性はDOMに存在しない。ただし、シリアライズ時に出力に追加される。
console. assert( ! plasticButton. hasAttribute( "is" ));
console. log( plasticButton. outerHTML); // will output '<button is="plastic-button"></button>'
作成方法に関係なく、button
が特別であるすべての方法は、そのような"プラスチックボタン"にも適用される:フォーカス動作、フォーム送信に関与する能力、disabled
属性など。
カスタマイズされた組み込み要素は、有用なユーザーエージェントが提供する動作またはAPIを持つ既存のHTML要素の拡張を可能にするように設計されている。そのため、この仕様で定義される既存のHTML要素のみを拡張することができ、要素インターフェイスとしてHTMLUnknownElement
を使用するように定義されたbgsound
、blink
、isindex
、keygen
、multicol
、nextid
、またはspacer
などのレガシー要素は拡張できない。
この要件の1つの理由は将来の互換性である。もしカスタマイズされた組み込み要素が、たとえばcombobox
のような、現在不明の要素を拡張したものが定義されたなら、派生したカスタマイズされた組み込み要素のコンシューマーが、ユーザーエージェント提供の動作に興味を持たない基本要素に依存するようになるため、これはこの仕様が将来combobox
要素を定義することを妨げるかもしれない。
以下で規定されているように、そして上記で触れたように、単にtaco-button
という要素を定義して使用しても、そのような要素がボタンを表すことを意味しない。つまり、ウェブブラウザー、検索エンジン、アクセシビリティ技術などのツールは、定義された名前だけに基づいて、結果の要素をボタンとして自動的に処理しない。
自律カスタム要素を依然として使用する一方で、様々なユーザーに所望のボタンセマンティックスを伝えるためためには、複数の技術を用いる必要がある:
tabindex
属性の追加は、taco-button
をフォーカス可能にする。もしtaco-button
を論理的に無効にするならば、tabindex
属性を削除する必要があるだろうことに注意すること。
ARIAロールならびにさまざまなARIAステートおよびプロパティの追加は、セマンティックスをアクセシビリティ技術に伝えるのに役立つ。 たとえば、roleを"button
"に設定すると、これがボタンであるというセマンティックスを伝え、支援技術で通常のボタンのような操作を使用してコントロールをユーザーが正常に操作できる。ボタンにアクセシブルな名前を付けるには、aria-label
プロパティを設定する必要がある。代わりに、アクセシビリティ技術でその子のテキストノードを縦走して通知する必要はない。そして、ボタンが論理的に無効になっているときにaria-disabled
ステートを"true
"に設定すると、アクセシビリティ技術にボタンの無効状態を伝える。
通常予想されるボタンの動作を処理するイベントハンドラーを追加することにより、ボタンのセマンティックスをウェブブラウザーのユーザーに伝えることを手伝う。この場合、最も関連性の高いイベントハンドラーは、適切なkeydown
イベントをclick
イベントとしてプロキシーするものであるので、キーボードとクリックの両方でボタンをアクティブにすることができる。
taco-button
要素に提供される任意のデフォルトの視覚スタイリングに加えて、視覚スタイリングはまた、例えば無効になるなど、論理状態の変化を反映するように更新される必要がある。つまり、taco-button
の規則を持つスタイルシートはすべて、taco-button[disabled]
の規則も必要となる。
これらの点を念頭に置いて、(無効にする機能を含む)ボタンのセマンティックスを伝える責任を負ったフル機能のtaco-button
は、次のようになる:
class TacoButton extends HTMLElement {
static observedAttributes = [ "disabled" ];
constructor() {
super ();
this . _internals = this . attachInternals();
this . _internals. role = "button" ;
this . addEventListener( "keydown" , e => {
if ( e. code === "Enter" || e. code === "Space" ) {
this . dispatchEvent( new PointerEvent( "click" , {
bubbles: true ,
cancelable: true
}));
}
});
this . addEventListener( "click" , e => {
if ( this . disabled) {
e. preventDefault();
e. stopImmediatePropagation();
}
});
this . _observer = new MutationObserver(() => {
this . _internals. ariaLabel = this . textContent;
});
}
connectedCallback() {
this . setAttribute( "tabindex" , "0" );
this . _observer. observe( this , {
childList: true ,
characterData: true ,
subtree: true
});
}
disconnectedCallback() {
this . _observer. disconnect();
}
get disabled() {
return this . hasAttribute( "disabled" );
}
set disabled( flag) {
this . toggleAttribute( "disabled" , Boolean( flag));
}
attributeChangedCallback( name, oldValue, newValue) {
// name will always be "disabled" due to observedAttributes
if ( this . disabled) {
this . removeAttribute( "tabindex" );
this . _internals. ariaDisabled = "true" ;
} else {
this . setAttribute( "tabindex" , "0" );
this . _internals. ariaDisabled = "false" ;
}
}
}
このかなり複雑な要素定義であっても、要素はカスタマーに使用する喜びとはならない。その要素は、自身の意志のtabindex
属性を継続的に"発芽"し、tabindex="0"
フォーカス機能の動作の選択は、現在のプラットフォームのbutton
動作と一致しなくてもよい 。これは、(通常、コンシューマーがデフォルトの動作を上書きできるように予約されているにもかかわらず)そのためにtabindex
属性の使用を強制する、現時点では、カスタム要素のデフォルトのフォーカス動作を指定する方法がないためである。
対照的に、前節で示したように、単一のカスタマイズされた組み込み要素は、button
要素のセマンティックスと動作を自動的に継承する。これらの動作を手動で実装する必要はない。一般に、HTMLの既存要素の上に構築される重要な動作やセマンティックスを持つ要素については、カスタマイズされた組み込み要素の開発、保守、および消費が容易になる。
要素定義はいつでも発生する可能性があるため、非カスタム要素が作成され、適切な定義を登録した後でカスタム要素にすることができる。 このプロセスを、通常の要素からカスタム要素に要素を"アップグレードする"と呼ぶ。
アップグレードは、パーサーなどで関連する要素が最初に作成された後に、カスタム要素定義を登録することが望ましい場合のシナリオを可能にする。これにより、カスタム要素内のコンテンツを段階的に強化することができる。例えば、次のHTML文書において、img-viewer
の要素定義は非同期的にロードされる:
<!DOCTYPE html>
< html lang = "en" >
< title > Image viewer example</ title >
< img-viewer filter = "Kelvin" >
< img src = "images/tree.jpg" alt = "A beautiful tree towering over an empty savannah" >
</ img-viewer >
< script src = "js/elements/img-viewer.js" async ></ script >
ここでのimg-viewer
要素の定義は、マークアップで<img-viewer>
タグの後に配置された、async
属性でマークされたscript
要素を用いて読み込まれる。 スクリプトの読み込み中、img-viewer
要素は、span
と同様に未定義の要素として扱われる。 スクリプトが読み込まれると、img-viewer
要素が定義され、ページ上の既存のimg-viewer
要素は、カスタム要素の定義が適用してアップグレードされる(これはおそらく、文字列"Kelvin"によって識別される画像フィルターの適用し、画像の視覚的外観を向上させることが含まれる )。
アップグレードは文書ツリー内の要素にのみ適用されることに注意する。(正式には、接続される要素。)文書に挿入されない要素は、アップグレードされないままである。例はこの点を説明する:
<!DOCTYPE html>
< html lang = "en" >
< title > Upgrade edge-cases example</ title >
< example-element ></ example-element >
< script >
"use strict" ;
const inDocument = document. querySelector( "example-element" );
const outOfDocument = document. createElement( "example-element" );
// Before the element definition, both are HTMLElement:
console. assert( inDocument instanceof HTMLElement);
console. assert( outOfDocument instanceof HTMLElement);
class ExampleElement extends HTMLElement {}
customElements. define( "example-element" , ExampleElement);
// After element definition, the in-document element was upgraded:
console. assert( inDocument instanceof ExampleElement);
console. assert( ! ( outOfDocument instanceof ExampleElement));
document. body. appendChild( outOfDocument);
// Now that we've moved the element into the document, it too was upgraded:
console. assert( outOfDocument instanceof ExampleElement);
</ script >
ユーザーエージェントによって提供される組み込み要素には、ユーザーインタラクションおよびその他の要因に応じて時間とともに変化する特定の状態があり、擬似クラスを通じてウェブ著者に公開される。たとえば、一部のフォームコントロールには"invalid"状態がある。これは、:invalid
疑似クラスによって公開される。
組み込み要素と同様に、カスタム要素もさまざまな状態を持つことができ、カスタム要素の著者は、組み込み要素と同様の方法でこれらの状態を公開したいと考える。
これは、:state()
疑似クラスによって行われる。カスタム要素の著者は、ElementInternals
のstates
プロパティを使用して、そのようなカスタム状態を追加および削除できる。これらのカスタム状態は、:state()
擬似クラスの引数として公開される。
次に、:state()
を使用して、カスタムチェックボックス要素のスタイルを設定する方法を示す。LabeledCheckbox
は、コンテンツ属性によって"checked"状態を公開しないと仮定する。
< script >
class LabeledCheckbox extends HTMLElement {
constructor() {
super ();
this . _internals = this . attachInternals();
this . addEventListener( 'click' , this . _onClick. bind( this ));
const shadowRoot = this . attachShadow({ mode: 'closed' });
shadowRoot. innerHTML =
`<style>
:host::before {
content: '[ ]';
white-space: pre;
font-family: monospace;
}
:host(:state(checked))::before { content: '[x]' }
</style>
<slot>Label</slot>` ;
}
get checked() { return this . _internals. states. has( 'checked' ); }
set checked( flag) {
if ( flag)
this . _internals. states. add( 'checked' );
else
this . _internals. states. delete ( 'checked' );
}
_onClick( event) {
this . checked = ! this . checked;
}
}
customElements. define( 'labeled-checkbox' , LabeledCheckbox);
</ script >
< style >
labeled-checkbox { border : dashed red ; }
labeled-checkbox : state ( checked ) { border : solid ; }
</ style >
< labeled-checkbox > You need to check this</ labeled-checkbox >
カスタム擬似クラスは、シャドウ部分をターゲットにすることもできる。上記の例を拡張すると、次のようになる:
< script >
class QuestionBox extends HTMLElement {
constructor() {
super ();
const shadowRoot = this . attachShadow({ mode: 'closed' });
shadowRoot. innerHTML =
`<div><slot>Question</slot></div>
<labeled-checkbox part='checkbox'>Yes</labeled-checkbox>` ;
}
}
customElements. define( 'question-box' , QuestionBox);
</ script >
< style >
question-box :: part ( checkbox ) { color : red ; }
question-box :: part ( checkbox ) : state ( checked ) { color : green ; }
</ style >
< question-box > Continue?</ question-box >
カスタム要素コンストラクターをオーサリングするとき、著者は次の適合性要件に拘束される:
それ以上のコードを実行する前に正しいプロトタイプチェーンおよびthis値を確立するために、パラメーターレスのsuper()
の呼び出しは、コンストラクター本体の最初のステートメントでなければならない。
return
ステートメントは、単純な早期復帰(return
またはreturn this
)でない限り、コンストラクター本体のどこにも現れてはならない。
コンストラクターはdocument.write()
またはdocument.open()
メソッドを使用してはならない。
非アップグレードの場合は存在しないため、要素の属性および子は検査してはならず、アップグレードに依存すると、要素の使用が困難になる。
これは、createElement
またはcreateElementNS
メソッドを使用するコンシューマーの期待に違反するため、要素は属性または子を取得してはならない。
一般に、作業は可能な限りconnectedCallback
に延期されるべきであり、特にリソースのフェッチまたはレンダリングに関連する作業はそうである。しかし、connectedCallback
は複数回呼び出すことができるため、本当に1回のみの初期化作業では、2回実行されないようにガードが必要になることに注意する。
一般に、コンストラクターは、初期状態およびデフォルト値を設定し、イベントリスナーおよび場合によってはシャドウルートを設定するために使用されるべきである。
これらの要件のいくつかは、要素の作成中に直接的または間接的のいずれかでチェックされ、それらに従わないと、パーサーまたはDOM APIによってインスタンス化できないカスタム要素をもたらす。これは、たとえコンストラクターによって開始されたマイクロタスク内で作業が行われるとしても当てはまる。マイクロタスクのチェックポイントは、作成直後に発生する可能性があるためである。
カスタム要素反応を作成する場合、予期しない結果が生じる可能性があるため、ノードツリーの操作を避けるべきである。
要素のconnectedCallback
は、要素が切断される前にキューに入れることができるが、コールバックキューはまだ処理されているため、接続されなくなった要素のconnectedCallback
をもたらす:
class CParent extends HTMLElement {
connectedCallback() {
this . firstChild. remove();
}
}
customElements. define( "c-parent" , CParent);
class CChild extends HTMLElement {
connectedCallback() {
console. log( "CChild connectedCallback: isConnected =" , this . isConnected);
}
}
customElements. define( "c-child" , CChild);
const parent = new CParent(),
child = new CChild();
parent. append( child);
document. body. append( parent);
// Logs:
// CChild connectedCallback: isConnected = false
When manipulating the DOM tree, an element can be moved in the tree while connected. This applies to custom elements as well. By default, the "disconnectedCallback
" and "connectedCallback
" would be called on the element, one after the other. This is done to maintain compatibility with existing custom elements that predate the moveBefore()
method. This means that by default, custom elements reset their state as if they were removed and re-inserted. In the example above, the impact would be that the observer would be disconnected and re-connected, and the tab index would be reset.
To opt in to a state-preserving behavior while moving, the author can implement a "connectedMoveCallback
". The existence of this callback, even if empty, would supercede the default behavior of calling "disconnectedCallback
" and "connectedCallback
". "connectedMoveCallback
" can also be an appropriate place to execute logic that depends on the element's ancestors. たとえば:
class TacoButton extends HTMLElement {
static observedAttributes = [ "disabled" ];
constructor() {
super ();
this . _internals = this . attachInternals();
this . _internals. role = "button" ;
this . _observer = new MutationObserver(() => {
this . _internals. ariaLabel = this . textContent;
});
}
_notifyMain() {
if ( this . parentElement. tagName === "MAIN" ) {
// Execute logic that depends on ancestors.
}
}
connectedCallback() {
this . setAttribute( "tabindex" , "0" );
this . _observer. observe( this , {
childList: true ,
characterData: true ,
subtree: true
});
this . _notifyMain();
}
disconnectedCallback() {
this . _observer. disconnect();
}
// Implementing this function would avoid resetting the tab index or re-registering the
// mutation observer when this element is moved inside the DOM without being disconnected.
connectedMoveCallback() {
// The parent can change during a state-preserving move.
this . _notifyMain();
}
}
カスタム要素は、カスタムである要素となる。非公式には、そのコンストラクターおよびプロトタイプが、ユーザーエージェントによってではなく、著者によって定義されていることを意味する。この著者が提供するコンストラクター関数をカスタム要素コンストラクターと呼ぶ。
2つの異なるタイプのカスタム要素を定義できる:
自律カスタム要素。これは、extends
オプションなしで定義されている。 これらのタイプのカスタム要素は、定義された名前と同じローカル名を持つ。
カスタマイズされた組み込み要素。これは、extends
オプションで定義されている。これらのタイプのカスタム要素は、extends
オプションで渡される値と同じローカル名を持ち、定義された名前はis
属性の値として使用され、したがってこれは妥当なカスタム要素名でなければならない。
カスタム要素が作成された後、is
属性の値を変更しても要素の動作は変更されない。
自律カスタム要素は、次の要素定義を持つ:
is
属性を除くform
、フォームに関連付けられたカスタム要素の場合 — 要素をform
要素に関連付けるdisabled
、フォームに関連付けられたカスタム要素の場合 — フォームコントロールが無効かどうかreadonly
、フォームに関連付けられたカスタム要素の場合 — willValidate
に影響し、カスタム要素の著者によって追加されたすべての動作name
、フォームに関連付けられたカスタム要素の場合 — フォームの送信およびform.elements
に使用する要素の名前HTMLElement
から継承)自律カスタム要素は特別な意味を持たない:その要素の子を表す。カスタマイズされた組み込み要素は、それが拡張した要素のセマンティックスを継承する。
要素の著者によって決定された、要素の機能に関連する名前空間なしの属性は、属性名がXML互換、かつASCII大文字を含まない限り、自律カスタム要素上で指定してもよい。例外はis
属性であり、これは自律カスタム要素に指定してはならない(かつ指定しても何の効果もない)。
カスタマイズされた組み込み要素は、それらが拡張する要素に基づいて、属性に関する通常の要件に従う。カスタム属性ベースの動作を追加するには、data-*
属性を使用する。
フォーム関連フィールドがtrueに設定されているカスタム要素定義に要素が関連付けられている場合、自律カスタム要素はフォームに関連付けられたカスタム要素と呼ばれる。
name
属性は、フォームに関連付けられたカスタム要素の名前を表す。disabled
属性は、フォームに関連付けられたカスタム要素を非対話的にし、その送信値が送信されないようにするために使用される。form
属性は、フォームに関連付けられたカスタム要素をそのフォーム所有者に明示的に関連付けるために使用される。
フォームに関連付けられたカスタム要素のreadonly
属性は、要素が制約検証から除外されることを指定する。ユーザーエージェントはこの属性のための他の動作を提供しないが、カスタム要素の著者は、可能であれば、組み込みフォームコントロール上のreadonly属性の動作と同様に、何らかの適切な方法でコントロールを編集不能にするために、その存在を使用すべきである。
制約検証。フォームに関連付けられたカスタム要素にreadonly
属性が指定されている場合、その要素は制約検証から除外される。
フォームに関連付けられたカスタム要素のリセットアルゴリズムは、要素、コールバック名"formResetCallback
"、および« »を使用してカスタム要素のコールバックリアクションをエンキューする。
妥当なカスタム要素名は、次のすべての要件を満たす一連の文字名である:
名前はPotentialCustomElementName
生成物と一致しなければならない:
PotentialCustomElementName ::=
[a-z] (PCENChar)* '-' (PCENChar)*
PCENChar ::=
"-" | "." | [0-9] | "_" | [a-z] | #xB7 | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x203F-#x2040] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]
これは、XML仕様由来のEBNF notationを使用している。[XML]
名前は次のいずれであってはならない:
annotation-xml
color-profile
font-face
font-face-src
font-face-uri
font-face-format
font-face-name
missing-glyph
上記の名前のリストは、適用可能な仕様、つまりSVG 2およびMathML由来のすべてのハイフンを含む要素名の要約である。[SVG] [MATHML]
これらの要件により、妥当なカスタム要素名の多数の目標が保証される。
それらはASCII小文字で開始し、HTMLパーサーがそれらをテキストではなくタグとして扱うことを保証する。
それらはASCII大文字が含まれないため、ユーザーエージェントは常にHTML要素を大文字・小文字不区別で扱うことができる。
名前空間および前方互換性を確保するために使用されるハイフンを含む(将来、ハイフンを含むローカル名を持つHTML、SVG、またはMathMLに要素が追加されなくなるため)。
それらは常にcreateElement()
およびcreateElementNS()
で作成できるが、これらにはパーサーの制限を超える制限がある。
これらの制限とは別に、<math-α>
や<emotion-😍>
などのユースケースに最大源の柔軟性を与えるために、さまざまな名前が許可されている。
CustomElementRegistry
インターフェイスSupport in all current engines.
各カスタム要素コンストラクターはHTMLElement
インターフェイスを継承しており、Window
オブジェクトごとに正確に1 つのHTMLElement
インターフェイスが存在するため、カスタム要素レジストリはDocument
オブジェクトの代わりに、Window
オブジェクトに関連付けられる。
window.customElements.define(name, constructor)
window.customElements.define(name, constructor, { extends: baseLocalName })
NotSupportedError
" DOMException
が投げられる。window.customElements.get(name)
window.customElements.getName(constructor)
window.customElements.whenDefined(name)
SyntaxError
" DOMException
で拒否されたプロミスを返す。window.customElements.upgrade(root)
要素定義は、CustomElementRegistry
にカスタム要素定義を追加するプロセスである。 これは、define()
メソッドによって実現される。
whenDefined()
メソッドを使用して、すべての適切なカスタム要素が定義されるまで、アクションを実行しないようにすることができる。この例において、使用する自律カスタム要素のすべてが定義されるまで、動的に読み込まれる記事のコンテンツを非表示するために、それを:defined
疑似クラスと組み合わせる。
articleContainer. hidden = true ;
fetch( articleURL)
. then( response => response. text())
. then( text => {
articleContainer. innerHTML = text;
return Promise. all(
[... articleContainer. querySelectorAll( ":not(:defined)" )]
. map( el => customElements. whenDefined( el. localName))
);
})
. then(() => {
articleContainer. hidden = false ;
});
upgrade()
メソッドは、自由に要素をアップグレード可能にする。 通常、要素は接続されたときに自動的にアップグレードされるが、この方法は、要素を接続する準備ができる前にアップグレードする必要がある場合に使用できる。
const el = document. createElement( "spider-man" );
class SpiderMan extends HTMLElement {}
customElements. define( "spider-man" , SpiderMan);
console. assert( ! ( el instanceof SpiderMan)); // not yet upgraded
customElements. upgrade( el);
console. assert( el instanceof SpiderMan); // upgraded!
カスタム要素は、著者のコードを実行することで特定の出来事に応答する機能がある:
アップグレードされると、引数なしでそのコンストラクターは実行される。
接続されると、connectedCallback
は引数なしで呼び出される。
接続が切断されると、そのdisconnectCallCallback
は引数なしで呼び出される。
When it is moved, its connectedMoveCallback
is called, with no arguments.
新しい文書に採用されると、古い文書と新しい文書を引数として指定して、adoptedCallback
が呼び出される。
属性のいずれかが変更、追加、削除、または置換されるとき、属性のローカル名、古い値、新しい値、および名前空間が引数として指定され、attributeChangedCallback
が呼び出される。(属性の古い値または新しい値は、その属性が追加または削除されたときにそれぞれnullと見なされる。)
ユーザーエージェントがフォームに関連付けられたカスタム要素のフォーム所有者をリセットし、それによってフォーム所有者を変更するとき、引数として新しいフォーム所有者(所有者がいない場合はnull)が指定されて、そのformAssociatedCallback
が呼び出される。
フォームに関連付けられたカスタム要素のフォーム所有者がリセットされるとき、そのformResetCallback
が呼び出される。
フォームに関連付けられたカスタム要素の無効状態が変更されるとき、新しい状態が引数として指定され、そのformDisabledCallback
が呼び出される。
ユーザーエージェントがユーザーに代わって、またはナビゲーションの一部としてフォームに関連付けられたカスタム要素の値を更新するとき、そのformStateRestoreCallback
が呼び出され、新しい状態と理由を示す文字列"autocomplete
"または"restore
"を引数として指定される。
これらの反応をまとめてカスタム要素応答と呼ぶ。
カスタム要素反応が呼び出される方法は、デリケートな操作の途中で著者のコードが実行されるのを避けるために、特別な注意を払って行われる。実質的に、"ユーザースクリプトに戻る直前"まで遅延される。これは、ほとんどの目的では同期して実行されているように見えるが、複雑な複合操作(クローニングや範囲操作など)の場合は、すべての関連するユーザーエージェントの処理ステップが完了するまで遅延され、その後、バッチとしてまとめて実行されることを意味する。
カスタム要素反応は常に、少なくとも1つのカスタム要素のローカルコンテキスト内で、トリガーアクションと同じ順序で呼び出されることが保証されている。(カスタム要素反応コードは独自の変更を実行できるため、複数の要素にまたがるグローバルな順序付けを保証することはできない。)
特定の機能は、カスタム要素の作成者が利用可能であることを意図されているが、カスタム要素の利用者は意図されていない。これらは、element.attachInternals()
メソッドによって提供され、ElementInternals
のインスタンスを返す。ElementInternals
のプロパティおよびメソッドは、ユーザーエージェントがすべての要素に提供する内部機能の制御を可能にする。
element.attachInternals()
Support in all current engines.
カスタム要素elementをターゲットとするElementInternals
オブジェクトを返す。elementがカスタム要素でない場合、"internals
"機能が要素定義の一部として無効にされた場合、または同じ要素で2回呼び出された場合、例外を投げる。
internals.shadowRoot
ターゲット要素がシャドウホストである場合、 internalsのターゲット要素のShadowRoot
を返し、そうでなければnullを返す。
internals.setFormValue(value)
internalsのターゲット要素の状態と送信値の両方をvalueに設定する。
valueがnullである場合、要素はフォームの送信に参加しない。
internals.setFormValue(value, state)
internalsのターゲット要素の送信値をvalueに設定し、その状態をstateに設定する。
valueがnullである場合、要素はフォームの送信に参加しない。
internals.form
internalsのターゲット要素のフォーム所有者を返す。
internals.setValidity(flags, message [, anchor ])
internalsのターゲット要素をflags引数で示された制約の影響を受けるものとしてマークし、要素の検証メッセージをmessageに設定する。anchorが指定される場合、ユーザーエージェントは、フォームの所有者がインタラクティブに検証される、またはreportValidity()
が呼び出されたときに、internalsのターゲット要素の制約に関する問題を示すために使用することがある。
internals.setValidity({})
internalsのターゲット要素をその制約を満たすものとしてマークする。
internals.willValidate
フォームの送信時にinternalsのターゲット要素が検証される場合はtrueを返す。そうでなければfalseを返す。
internals.validity
internalsのターゲット要素のValidityState
オブジェクトを返す。
internals.validationMessage
もしinternalsのターゲット要素の妥当性をチェックしたならば、ユーザーに表示されるエラーメッセージを返すだろう。
valid = internals.checkValidity()
internalsのターゲット要素に妥当性の問題がない場合はtrueを返す。 そうでなければfalseを返す。 後者の場合要素でinvalid
イベントを発火する。
valid = internals.reportValidity()
internalsのターゲット要素が一切妥当性の問題を持たない場合はtrueを返す。そうでなければfalseを返し、要素でinvalid
イベントを発火させ、そして(イベントが中止されない場合)ユーザーに問題を報告する。
internals.labels
それぞれのフォームに関連付けられたカスタム要素は送信値を持つ。フォーム送信時に1つ以上のエントリーを提供するために使用される。送信値の初期値はnullであり、送信値はnull、文字列、File
、またはエントリーのリストにすることができる。
それぞれのフォームに関連付けられたカスタム要素は状態を持つ。これは、ユーザーエージェントが要素に対するユーザーの入力を復元できる情報である。状態の初期値はnullであり、状態はnull、文字列、File
,、またはエントリーのリストにすることができる。
カスタム要素の著者はsetFormValue()
メソッドを使用して要素の送信値と状態を設定し、これらをユーザーエージェントに伝達する。
ユーザーエージェントが、たとえばナビゲーション後やユーザーエージェントの再起動など、フォームに関連付けられたカスタム要素の状態を復元するのがよいと考えている場合、その要素、コールバック名"formStateRestoreCallback
"、および"restore"
をもつカスタム要素のコールバック反応をエンキューしてもよい。
ユーザーエージェントがフォーム記入アシスト機能を持っている場合、その機能が呼び出されたときに、フォームに関連付けられたカスタム要素、コールバック名"formStateRestoreCallback
"、«状態値の履歴といくつかのヒューリスティックによって決定された状態値、および"autocomplete"
»とともにフォームに関連付けられたカスタム要素のコールバック反応をエンキューしてもよい。
一般に、状態はユーザーによって指定された情報であり、送信値はサーバーへの送信に適した、正規化またはサニタイズ後の値である。 次の例は、これを具体的に示している:
ユーザーに日付の指定を求めるフォームに関連付けられたカスタム要素があるとする。 ユーザーは"3/15/2019"を指定するが、コントロールはサーバーに"2019-03-15"
を送信しようとする。"3/15/2019"は要素の状態であり、"2019-03-15"
は送信値である。
既存のチェックボックスinput
タイプの動作をエミュレートするカスタム要素を開発するとする。 その送信値は、そのvalue
コンテンツ属性の値、または文字列"on"
になる。その状態は、"checked"
、"unchecked"
、"checked/indeterminate"
、または"unchecked/indeterminate"
のいずれかになる。
internals.role [ = value ]
internalsのターゲット要素のデフォルトのARIAロールを設定または取得する。これは、ページの著者がrole
属性を使用して上書きしない限り、使用される。
internals.aria* [ = value ]
internalsのターゲット要素のさまざまなデフォルトのARIAステートまたはプロパティを設定または取得する。これは、ページの著者がaria-*
属性を使用してそれらを上書きしない限り、使用される。
ElementInternals
のrole
およびaria*
プロパティを使用することで、カスタム要素の著者は、ネイティヴ要素がどのように振る舞うかに類似の、カスタム要素にデフォルトのアクセシブルなロール、ステート、およびプロパティ値を設定することができる。詳細は上記の例を参照のこと。
internals.states.add(value)
擬似クラスとして公開されるように、要素の状態セットに文字列valueを追加する。
internals.states.has(value)
valueが要素の 状態セット内にある場合はtrue、そうでなければfalseを返す。
internals.states.delete(value)
要素の状態セットがvalueを持つ場合、その状態セットは削除され、trueが返される。そうでなければ、falseを返す。
internals.states.clear()
要素の状態セットからすべての値を削除する。
for (const stateName of internals.states)
for (const stateName of internals.states.entries())
for (const stateName of internals.states.keys())
for (const stateName of internals.states.values())
要素の状態セット内のすべての値に対して繰り返す。
internals.states.forEach(callback)
値ごとに1回callbackを呼び出すことにより、要素の状態セット内のすべての値に対して反復処理を行う。
internals.states.size
要素の状態セットの値の数を返す。
状態セットは、文字列値の有無によって表される真偽値状態を公開できる。著者が3つの値を持つことができる状態を公開したい場合、3つの排他的な真偽値状態に変換できる。たとえば、"loading"
、"interactive"
および"complete"
値をもつreadyState
という状態は、3つの排他的な真偽状態"loading"
、"interactive"
および"complete"
マップされてもよい:
// Change the readyState from anything to "complete".
this . _readyState = "complete" ;
this . _internals. states. delete ( "loading" );
this . _internals. states. delete ( "interactive" );
this . _internals. states. add( "complete" );