Webコンポーネント。 パート1:カスタム要素

エントリー


この記事は、ネイティブHTMLおよびJSツールを使用したWebコンポーネントの作成に関する一連の記事の最初の部分です。







Webアプリケーションの開発に対するコンポーネントアプローチは、再利用でき、共通の機能によって結合され、また状態を保存および復元し、他のコンポーネントと相互作用し、同時に他のコンポーネントに依存しない独立したコードモジュールの作成に依存します。







このアプローチを実装するために、現在3つの仕様が開発されています。最初の仕様については、この記事で説明します。 それでは、 カスタム要素仕様について知りましょう。その作業ドラフトは2016年10月13日に公開され、最新バージョンは2017年4月4日付です。







ユーザー要素は、Webコンポーネントパッケージに含まれるAPIの最も重要な部分です。これは、以下の主要な機能を提供するためです。









一般的に



CustomElementRegistryインターフェイスは、カスタムWebページ要素の作成を担当します。これにより、要素を登録したり、登録された要素に関する情報を返したりできます このインターフェイスは、window.customElementオブジェクトとして使用できます。このオブジェクトには、3つの興味深いメソッドがあります。









仕様の公開バージョンでは、2つの形式のいずれかでカスタム要素を作成できます。自律型カスタム要素(自律ユーザー要素)とカスタマイズされた組み込み要素(カスタム組み込み要素)。







違いについて



自律ユーザー要素には機能がなく、仕様に従って、フレーズおよびストリーミングコンテンツでの使用が期待され、is属性以外の属性を受け取ることができます。これについては後で説明します。 そのような要素のDOMインターフェースは、著者によって決定されるべきです。 要素はHTMLElementを継承します。







次に、extendsプロパティ(extends)でカスタムインライン要素を定義する必要があります。 したがって、作成されたカスタム要素は、extendsプロパティの値で指定された要素のセマンティクスを継承する機能を取得します。 この機能の必要性は、HTML要素のすべての既存の動作をスタンドアロン要素のみを使用して複製できるわけではないため、仕様の作成者によって規定されています。

違いは、要素を宣言する構文とその使用でも顕著ですが、例(つまり、この記事の後の方)を使用する方がはるかに簡単です。







属性について



カスタム属性はis属性に固有であり、カスタマイズされた埋め込み要素(宣言された要素)の名前の値を取ります。

当然、is属性は、スタンドアロンの要素で宣言されている場合(仕様に従って実行できない)、効果はありません。



それ以外の場合、両方のタイプの要素の属性は、XML互換( www.w3.org/TR/xml/#NT-Nameに対応し、U + 003A-コロンを含まない)およびASCII大文字( https://html.spec.whatwg.org/multipage/infrastructure.html#uppercase-ascii-letters)。







定義



カスタムアイテム定義には以下が含まれます。













  1. 現在の仕様によれば、名前は次の形式に対応する場合に有効です。







    [az] (PCENChar)* '-' (PCENChar)* PCENChar ::= "-" | "." | [0-9] | "_" | [az] | #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)表記(https://www.w3.org/TR/xml/#sec-notation)。

    簡単な場合は、小文字のASCII文字で始まり、大文字を含まず、少なくとも1つのハイフンで区切られます。



    名前に次の値を含めることはできません:annotation-xml、color-profile、font-face、font-face-src、font-face-uri、font-face-format、font-face-name、missing-glyph。







  2. ローカル名







    スタンドアロンユーザー要素の場合、これは定義からの名前(定義名)であり、カスタムインライン要素の場合、その拡張オプションに渡される値(定義からの名前は属性の値として使用されます)







  3. コンストラクター







    コンストラクターは、インスタンスの作成またはアップグレード時に呼び出され、状態の初期化、オブザーバーの設定、またはシャドウdomの作成に適しています。 ただし、 いくつかの制限があります。 したがって、コンストラクター本体の最初の呼び出しは、パラメーターなしのsuper()の呼び出しでなければなりません。 returnキーワードは、通常のアーリーリターン(returnまたはreturn this)でない限り、コンストラクターの本体に表示されるべきではありません。 document.write()またはdocument.open()を呼び出さないでください。この段階で子孫と属性を作成したり、アクセスしたりしないでください。 本当に一度だけ行う必要がある作業のみをここで行う必要があり、他の可能なすべてはconnectedCallbackに送信する必要があります(以下を参照)。







  4. プロトタイプ、JSオブジェクト







  5. 観察された属性のリスト







    変更によりattributeChangedCallbackメソッドが呼び出される属性のリスト(以下を参照)。 静的ゲッターによって定義され、文字列値の配列を返す必要があります。







  6. ライフサイクルメソッドコレクション







    コンポーネントのライフサイクルに対応する4つの方法が示されています。







    • connectedCallback

      要素がDOMに埋め込まれるたびに呼び出されます。 ここでは、リソースをリクエストしてレンダリングするのが適切です。 ほとんどの作業は、この方法に最適です。







    • 切断された

      DOMから要素が削除されるたびに呼び出され、メモリを解放するために使用されます(要求のキャンセル、間隔のキャンセル、タイマー、ハンドラーなど)。







    • 採用された

      たとえばdocument.adoptNode()を呼び出して、アイテムが新しいドキュメントに移動したときに呼び出されます。







    • attributeChangedCallback

      observeAttributesリストに含まれる属性を追加、変更、または置換するたびに呼び出されます。このメソッドは、変更された属性の名前、古い値、新しい値の3つの引数で呼び出されます。









    割り当てられていない可能性があります 仕様では、その値に関数またはnullを提供しています。 すべてのセットアップコールバックは同期的に呼び出されます。







  7. 建設用スタック







    最初は空のリストで、要素アルゴリズムとHTML要素コンストラクターのアップグレードによって変更されます 。これらの各出現は、後で要素または既に作成されたマーカーであることが判明します。







続きを読む:スタンドアロンのカスタム要素



最小限の作成構文は簡単です:

HTMLElementクラスを拡張するクラスが作成されます。 将来のコンポーネントのマークアップは、connectedCallback内のthis.innerHTMLで設定されます。







 class AcEl extends HTMLElement { connectedCallback() { this.innerHTML = `<p>I'm an autonomous custom element</p>`; } }
      
      





クラスを宣言した後、次を呼び出して要素を決定する必要があります。







 customElements.define('ac-el', AcEl);
      
      





単純な動作を追加した例:







 class TimerElement extends HTMLElement { connectedCallback() { this.render(); this.interval = setInterval(() => this.render(), 1000); } disconnectedCallback() { clearInterval(this.interval); // } render() { this.innerHTML = ` <div>${new Date().toLocaleString({hour: '2-digit', minute: '2-digit', second: '2-digit' })}</div> `; } } customElements.define('timer-element', TimerElement);
      
      





自律ユーザー要素は、タグとして指定することで使用できます。









 <timer-element></timer-element>
      
      





または







 const timer = document.createElement('timer-element');
      
      





または







 const timer = new TimerElement(); document.body.appendChild(timer);
      
      





開発者のツールでタイマーを観察すると、ページが過負荷にならず、DOMへの変更が個別に行われていることがわかります。 反応を非常に連想させる







情報提供の目的で、一連の記事全体を通してカスタムタブ要素を作成することを計画しました。 この段階では、技術的なタスクは非常に単純に見えます。 タブは、任意の数のタブで構成する必要があります(ナビゲーション要素の数がタブの数と一致する場合)。







*将来を見据えて、スタイルのカプセル化、およびタブとラベルの両方のコンテンツをより自由に設定する方法については、次の記事で説明します。







そのため、ナビゲーション要素、コンテンツ要素、ラッパー要素の3つのカスタム要素を作成する予定です。 ナビゲーション要素はターゲット属性を受け入れ、そのコンテンツは要素を対応するナビゲーション要素に関連付け、同時にナビゲーション要素のテキストとして表示されます。 実装:







 class TabNavigationItem extends HTMLElement { constructor() { super(); //      this._target = null; } connectedCallback() { this.render(); // } static get observedAttributes() { return ['target']; } //       attributeChangedCallback attributeChangedCallback(attr, prev, next) { if(prev !== next) { this[`_${attr}`] = next; this.render(); } } //         render() { if(!this.ownerDocument.defaultView) return; this.innerHTML = ` <a href="#${this._target}">${this._target}</a> `; } }
      
      





タブ要素を作成するためのクラスには、ターゲット属性が必要です。その値は、要素をナビゲーション要素に関連付け、この場合はタブのコンテンツが転送される属性(コンテンツは属性に渡されます-ユーザーはタブを使用する可能性が非常に制限されますが、より柔軟なアプローチの実装次の記事で実装します)。







 class TabContentItem extends HTMLElement { constructor() { super(); this._target = null; this._content = null; } connectedCallback() { this.render(); } static get observedAttributes() { return ['target', 'content']; } attributeChangedCallback(attr, prev, next) { if(prev !== next) { this[`_${attr}`] = next; this.render(); } } render() { if(!this.ownerDocument.defaultView) return; this.innerHTML = ` <div>${this._content}</div> `; } }
      
      





ラッパー要素自体には機能的なロジックが含まれます。すべてのナビゲーション要素を受け取り、クリックしてハンドラーをイベントに添付します。_targetプロパティは、目的のタブを決定して表示します。







 class TabElement extends HTMLElement { connectedCallback() { this.listener = this.showTab.bind(this); this.init(); } disconnectedCallback(){ this.navs.forEach(nav => nav.removeEventListener('click', this.listener)); } showTab(e) { e.preventDefault(); e.stopImmediatePropagation(); const target = e.target.closest('tab-nav-item')._target; [...this.tabs, ...this.navs].forEach(el => { if (el._target === target) el.classList.add('active'); else el.classList.remove('active'); }); } init() { this.navs = this.querySelectorAll('tab-nav-item'); this.tabs = this.querySelectorAll('tab-content-item'); this.navs.forEach(nav => nav.addEventListener('click', this.listener)); } }
      
      





*更新、ハンドラーの削除の修正







最後の、しかし最も重要なステップは、要素の宣言です:







 customElements.define('tab-element', TabElement); customElements.define('tab-nav-item', TabNavigationItem); customElements.define('tab-content-item', TabContentItem);
      
      





作業タブの例はこちらにあります。







詳細:カスタマイズされたインライン要素



カスタマイズされたインライン要素には、スタンドアロンのユーザー要素とは2つの違いがあります。要素はHTML要素の組み込みクラスを継承でき、そのような要素を宣言すると、.define()メソッドの3番目の引数が必須になります。

この例を考えてみましょう:







 class JumpingButton extends HTMLButtonElement { constructor() { super(); this.addEventListener("hover", () => { // animate here }); } } customElements.define('jumping-button', JumpingButton, { extends: 'button' });
      
      





このような要素は、HTMLButtonElementのセマンティクスを継承し、それを展開できます。

要素を使用する場合、埋め込みHTML要素のタグが示されます。この例では、属性を持つボタンがあり、ユーザー要素の定義からの名前が渡されます。したがって、







 <button is="jumping-button">Click Me!</button>
      
      





jsメソッドによる作成は次のようになります。







 const jb = document.createElement("button", { is: "jumping-button" });
      
      





そのような要素の.localNameは、.localNameが定義からの名前に等しいスタンドアロンのユーザー要素とは対照的に、「ボタン」になることを忘れないことをお勧めします。







これまで、単一のブラウザがカスタマイズされた組み込み要素を実装していないため、例を理論的に考慮する必要があります。







アップグレードに関する結論や2語の代わりに



ユーザー定義要素の定義をCustomElementRegistryに追加するため(これはdefine()メソッドの呼び出しに依存します)、いつでも発生する可能性があるため、通常の(非ユーザー)要素を作成でき、その後、対応する定義を登録した後にユーザー定義要素になります(呼び出し.define())。 アップグレードアルゴリズムは、対応する要素が最初に作成された後にユーザー要素の定義を登録することが望ましいイベントのコースを提供します。 これにより、カスタム要素のコンテンツのプログレッシブエンハンスメントを実装できます。 同時に、集計はDOMツリーの要素でのみ使用できます(つまり、シャドウDOMの場合、shadowRootはドキュメント内に存在する必要があります)。









次の記事: Webコンポーネント。 パート2:シャドウDOM







3番目の記事: Webコンポーネント。 パート3:HTMLテンプレートとインポート







厳しく判断しないでください。 よろしくTania_N








All Articles