🏙️
Web Components を実装した
カプセル化したかった。
- カスタム要素を登録
- カスタム要素のクラスには Shadow DOM とライフサイクルコールバックを追加
HTML テンプレートは使わなかった。
カスタム要素
新しい要素を定義できる。
class MarkdownElement extends HTMLElement {}
customElements.define("my-markdown", MarkdownElement);
ここでは my-markdown
要素を定義した。(要素名には多少の制限がある)
Shadow DOM
カプセル化の主要部分。
class MarkdownElement extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: "open"});
}
}
Element.attachShadow
は ShadowRoot
への参照を返すが、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 は(基本的に)外のスタイリングに影響しない/されない。
- 継承はできる
- CSS カスタムプロパティも継承できる
-
:host
疑似クラスや::part
疑似要素で Shadow DOM の内外からスタイリングできる(こともある)
ライフサイクルコールバック
要素のライフサイクルに応じて呼び出される。
例えば、
-
observedAttributes
で属性を指定すると - 指定した属性が変更されたときに
attributeChangedCallback
が呼び出される。
class MarkdownElement extends HTMLElement {
static get observedAttributes() {
return ["src"];
}
attributeChangedCallback(
name,
oldValue,
newValue,
) {
if ("src" !== name) {
return;
}
}
}
attributeChangedCallback
は
- 変更された属性の名前(
name
) - 変更前の値(
oldValue
) - 変更後の値(
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