LoginSignup
0

LitElementでLight DOMのWeb Componentを作る方法

はいさい!ちゅらデータのオースティンやいびーん!

概要

Web Componentsの作成を助けてくれるLitでShadow DOMではなく、Light DOMにレンダーする部品を作る方法を紹介します!

背景

LitElementで部品を作ると、デフォルトでShadow DOMを追加し、そのShadow DOMにHTMLエレメント、CSSのスタイルなどをレンダーしていきます。

しかし、Shadow DOMではなく、Light DOMにレンダーしたい場合があります。

  1. グローバルCSSを適応したい時
  2. ログインフォームでブラウザのキーマネージャーと相性良く付き合いたい時(Chrome、SafariはShadow DOMに対してパスワードの自動入力・保存ができないのです)
  3. Light DOMの<form>に入る入力部品を作りたい時

こういう時に以下の方法でLight DOMにレンダーしていただけます!

コード

LitElementは、customElement.defineでブラウザに登録された時に、内蔵化されているLitElement.createRenderRootを実行して、部品のHTMLをどこに入れるかを定めます。

この関数を以下のように上書きすると、this.shadowRootではなく、thisをRenderRootに指定することができます。

src/lit-element.ts
import { LitElement, html } from "lit";

export default class LitComponent extends LitElement {
  protected createRenderRoot() {
    return this; // Light DOMを使うように
    // thisがLight DOMの<lit-component>のエレメントに当たります
  }

  #handleSubmit: EventListener = (event) => {
    event.preventDefault();
  };

  render() {
    return html`
      <h1>Lit Form</h1>
      <form @submit=${this.#handleSubmit}>
        <label for="name">名前</label>
        <input type="text" id="name" name="name" />
      </form>
    `;
  }
}

これで、この部品はLight DOMにレンダーされるようになります!

実際にブラウザで見てみよう。

Webpackなどのバンドラーのエントリーポイントで上記のLitComponentを読み込んで、customElements.defineでブラウザに登録します。

src/index.ts
import LitComponent from "./lit-component"

customElements.define("lit-component", LitComponent);

また、HTMLのテンプレートに<lit-element>を入れます。また、Light DOMの<style>タグでh1の文字の色を赤にしています。

<lit-element>内の<h1>が赤くなっていれば、Light DOMにレンダーできていることがわかります。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <style>
    h1 {
      color: red;
    }
  </style>
  <lit-component></lit-component>
</body>

</html>

これをビルドして、ブラウザで開いてみると、スクリーンショット 2022-07-08 11.53.36.png
出た!赤いね。

そして、InspectorでHTMLを見てみると、
スクリーンショット 2022-07-08 11.54.33.png
Shadow DOMはなく、Light DOMにレンダーされていることが確認できます。

注意点: LitElementのstylesが使えなくなる!

このように、Light DOMにレンダーすると、通常のLitElementのCSSを指定するstatic styles = css``が使えなくなります。

通常は、以下のように書きます。

import { LitElement, html, css } from "lit";

export default class LitComponent extends LitElement {
  #handleSubmit: EventListener = (event) => {
    event.preventDefault();
  };

  static styles = css`
    input {
      height: 40px;
    }
  `;

  render() {
    return html`
      <h1>Lit Form</h1>
      <form @submit=${this.#handleSubmit}>
        <label for="name">名前</label>
        <input type="text" id="name" name="name" />
      </form>
    `;
  }
}

しかし、上記のLight DOMをRenderRootに指定する方法だと、static stylesが反映されません。

なぜなら、実は、LitElement.createRenderRootでは、デフォルトで以下のようなコードを実行しているからです。

import { LitElement, html, css, adoptStyles } from "lit";

...

protected createRenderRoot() {
    this.attachShadow({ mode: "open" });
    adoptStyles(this.shadowRoot!, [LitComponent.styles])
    return this.shadowRoot; 
}

このcreateRenderRootを上書きすると、lit.adoptStylesがなくなってしまいます。もっと残念な知らせですが、このadoptStylesにthisだけを渡すことができないようです。

これで、以下のような工夫が必要になります。

import { LitElement, html, css } from "lit";

export default class LitComponent extends LitElement {
  protected createRenderRoot() {
    return this; // Light DOMを使うように
    // thisがLight DOMの<lit-component>のエレメントに当たります
  }

  #handleSubmit: EventListener = (event) => {
    event.preventDefault();
  };

  render() {
    const styles = css`
      input {
        height: 40px;
      }
    `;
    return html`
      <style>${styles}</style>
      <h1>Lit Form</h1>
      <form @submit=${this.#handleSubmit}>
        <label for="name">名前</label>
        <input type="text" id="name" name="name" />
      </form>
    `;
  }
}

まとめ

これで、LitElementをShadow DOMからLight DOMにレンダーするように指定する方法を紹介してきましたが、いかがでしょうか?

次の記事では、Shadow DOMとLight DOMの両方にレンダーする方法を紹介します!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
0