Web Componentがdefineされるまでの無を回避したい

2021/08/24に公開

Web Component(Custom Element)がdefineされる時のガタつきを回避したい。

Custom Elementがdefineされるまでdivな問題

<x-button>ボタンだよ</x-button>

こういうWeb Componentで定義されたボタンがあるとしますね

これは、コンポーネントがdefineされるまでこの状態です

つまりスクリプトが読み込まれてcustomElements.define()が実行されるまで、<x-button>の見た目は<div>と同じなのです。Slotが無いコンポーネントに至っては無です。

Custom Elementは内部にロジック・スタイル・HTML構造を持っており、その情報はスクリプトが実行されるまで空のままなので仕方がありません。

これの何が問題かと言うと、Web ComponentsがWebページ内に沢山並べられている場合、スクリプト実行前はCSS読み込み前のような見た目がユーザーに対して表示されてしまいます。そしてスクリプト実行次第ガチャガチャとデザインが整えられていく様を見せることになります。現象としては一瞬なのですが、ダサいことは否めません。

スクリプトタグを<body>より上部に設置してそこでレンダリングをストップすれば良いのですが、モダンなWebではそうも言ってられないでしょう。

回避策

ファーストビューにWeb Componentsを置かない

モーダルとかクリックしないと出てこないドロップダウンの部分とか。あとはfetchでデータ取得してから表示する部分はスクリプトが前提になるので気軽に使えます。

もしくは使う分全てがdefineされるまでページ全体または使っている部分に蓋をするのも有効です。Page Loading Stateですね。しかし「使用している全てのWeb Componentsがdefineされた」というフラグをどう立てるのか、少しロジックが必要になります。

蓋をするためのコンポーネントもWeb Componentsでできていたら...?
恐ろしいですね。

Stencilを使う

StencilはなんとSSR + hydrationの機能が備わっています。

Hydrate App - Stencil

具体的な手順は

  1. ShadowDOMをLightDOMに変換して出力
  2. 内部にあるCSSをグローバルな<style>に設置
  3. スクリプトが実行され、コンポーネントが順次defineされる
  • LightDOMに設置されたDOMはここで上書きされる
  1. コンポーネントがdefineされ次第defined attributeをコンポーネントに付ける
  • 設置されたstyleには全て:not[defined]が付いているため、define済みコンポーネントには適用されない
  1. hydration完了

2番目のShadow DOM内に設置されたはずのCSSを全てグローバル空間に展開している部分が結構マジックですね。
とてもスマートです。Stencilを選ばなければこの機能は使えません。

グローバルなCSSで一旦お茶を濁す

Stencilのhydrationに似ていますが、これはその簡易版のようなもので。

コンポーネントのサイズ、色などはある程度設置されたattributesから類推できます。

<x-button size="small" color="red">ボタンだよ</x-button>

なのでグローバルなCSS空間にdefine前のためのstyleを設置しておきます。

x-button:not[defined] {
  ...
  &.small {
    padding: 4px 8px;
    font-size: 14px;
    ...
  }
}

そしてWeb Componentsの方にdefine完了時点でCSSを外すためのattributeを追加する処理を書きます。

connectedCallback() {
  this.setAttribute('defined', '');
}

スタイルの実装としては完全に二度手間です。本来のCSSとグローバルなCSS、二回書かなければいけないため、メンテナンスコストも増すでしょう。

面倒な場合は色などはPlaceholder扱いで灰色に統一などでも良いでしょう。重要なのはページレイアウトがガタつくのを防ぐことです。

まとめ

色々書きましたがいまいちこれをやれば簡単OKみたいな方法は見つかっていません。誰か教えて下さい。Stencilは便利だけど傾向としては皆Lit使っていますし、実際のところ一つや二つのコンポーネントを個別に読み込むだけなら描画までの時間は気にならないほどでしょう。

しかしブラウザ機能によるページ更新などで見ると明確なガタつきが見れてしまうため、Web Componentsをプロダクション運用する時は何か対応が必要になると思います。

Discussion