🏙️

Web Components を実装した

2022/07/15に公開約3,900字

カプセル化したかった。

  1. カスタム要素を登録
  2. カスタム要素のクラスには Shadow DOMライフサイクルコールバックを追加

HTML テンプレートは使わなかった。

カスタム要素

新しい要素を定義できる。

  1. HTMLElement を継承したクラス
  2. customElements.define で登録する
class MarkdownElement extends HTMLElement {}
customElements.define("my-markdown", MarkdownElement);

ここでは my-markdown 要素を定義した。(要素名には多少の制限がある

Shadow DOM

カプセル化の主要部分。

  1. Element.attachShadow で Shadow DOM を追加
  2. Element.shadowRoot に要素を追加
class MarkdownElement extends HTMLElement {
    constructor() {
        super();
        this.attachShadow({mode: "open"});
    }
}

Element.attachShadowShadowRoot への参照を返すが、Element.shadowRoot でもアクセスできる。

this.attachShadow({mode: "open"});
const css = document.createElement("link");
css.rel = "stylesheet";
css.href = "style.css";
this.shadowRoot.appendChild(css);

ここでは CSS を追加した

スタイリングは link 要素でも style 要素でもできる。

const style = document.createElement("style");
style.textContent = `p {
    text-indent: var(--text-indent, 0);
}`;
this.shadowRoot.appendChild(style);

Shadow DOM は(基本的に)外のスタイリングに影響しない/されない。

ライフサイクルコールバック

要素のライフサイクルに応じて呼び出される。

例えば、

  1. observedAttributes で属性を指定すると
  2. 指定した属性が変更されたときに attributeChangedCallback が呼び出される。
class MarkdownElement extends HTMLElement {
    static get observedAttributes() {
        return ["src"];
    }
    attributeChangedCallback(
        name,
        oldValue,
        newValue,
    ) {
        if ("src" !== name) {
            return;
        }
    }
}

attributeChangedCallback

  1. 変更された属性の名前(name
  2. 変更前の値(oldValue
  3. 変更後の値(newValue

を受け取る。

HTML で URL を指定するだけで、

<my-markdown src="hello-world.md"></my-markdown>

Markdown を読み込み、変換して表示することもできる。

if ("src" !== name || null === (newValue ?? null)) {
    return;
}
fetch(newValue)
.then(async response => {
    if (!response?.ok || "basic" !== response.type) {
        throw new Error("Response NG");
    }
    return await response.text();
})
.then(markdown => {
    // Markdown を HTML に変換する
    // 何らかの処理
    return markdown_to_html(markdown);
})
.then(html => {
    let body = this.shadowRoot.querySelector("#body");
    if (!body) {
        body = document.createElement("div");
        body.id = "body";
        this.shadowRoot.append(body);
    }
    body.innerHTML = html;
})
.catch(console.error);

Discussion

ログインするとコメントできます