おう、諸君、調子はどうだ? フロントエンドの最前線で日々奮闘する諸君らに、今日はちょっとニッチだが、知っておくと「なるほど!」と膝を打つようなCSSのテクニックを伝授しようと思う。Web Components、特にカスタム要素(Custom Elements)を扱う上で、避けては通れない「`:defined` 疑似クラス」の話だ。
「`:defined`? なんだそれは?」と思った君、鋭い。これは比較的新しい疑似クラスで、まだ全ての環境でバリバリ使われているわけではない。しかし、Web Componentsの普及とともに、その重要性は増す一方だ。今日は、この`:defined`疑似クラスの正体、ブラウザがどうやってそれを判定しているのか、そして何よりも、現場で「使える」コードを、実践的なサンプルを交えながら、わかりやすく解説していく。
:defined 疑似クラスの正体:カスタム要素の「今」を知る鍵
さて、まずは`:defined`疑似クラスが何者なのか、その核心に迫ろう。
簡単に言えば、`:defined`疑似クラスは、「ブラウザにカスタム要素が定義済みかどうか」 を選択するためのCSSセレクタだ。
Web Componentsは、再利用可能なカスタムHTMLタグを自作できる強力な仕組みだ。例えば、こんなカスタム要素を定義したとしよう。
// custom-button.js
class CustomButton extends HTMLElement {
constructor() {
super();
// … ボタンの内部実装 …
}
}
customElements.define(‘custom-button’, CustomButton);
このJavaScriptが実行され、`customElements.define()`によって`custom-button`というタグがブラウザに「登録」されると、このカスタム要素は「定義済み」となる。
`:defined`疑似クラスは、まさにこの「定義済み」の状態をCSSで捉えるためのものなんだ。
なぜ「定義済み」を知る必要があるのか?
君たちが普段書いているHTMLやCSSは、ブラウザが読み込んで即座にレンダリングできるものばかりだろう。しかし、Web Components、特にカスタム要素は、JavaScriptによって動的に定義されることが多い。
ここで問題になるのが、「JavaScriptによるカスタム要素の定義が完了する前に、その要素のスタイルをどう指定するか?」 という点だ。
例えば、カスタム要素が読み込まれて定義されるまでの間は、デフォルトのスタイルを適用したい。あるいは、定義されたら、よりリッチなスタイルを適用したい、といったニーズが出てくる。
ここで`:defined`疑似クラスが真価を発揮する。
- `:defined` が付かない場合: カスタム要素がまだ定義されていない、または定義される前の状態。
- `:defined` が付く場合: カスタム要素がブラウザに定義され、使用可能な状態。
これを使えば、Web Componentsの読み込み前後で、まるで魔法のようにスタイルを切り替えることができるんだ。
ブラウザの裏側:`:defined` はどうやって動いているのか?
「ふむ、便利そうだが、ブラウザはどうやってカスタム要素が定義されたことを知るんだ?」という疑問が湧くだろう。これは、カスタム要素の仕様とブラウザのレンダリングプロセスが密接に関わっている部分だ。
ブラウザは、HTMLをパースする際に、未知のタグ(カスタム要素のタグ)に遭遇すると、それを一旦「未定義のカスタム要素」として扱う。そして、JavaScriptの実行を待つ。
JavaScriptによって`customElements.define()`が実行されると、ブラウザのカスタム要素レジストリにそのタグ名とクラスが紐づけられる。この「登録」が完了した瞬間、ブラウザは内部的にそのカスタム要素が「定義済み」になったことを認識する。
CSSのパーサーは、このブラウザの内部状態を監視しており、`:defined`セレクタに遭遇した際に、その要素が定義済みのカスタム要素であればマッチさせ、そうでなければマッチさせない、という処理を行っているんだ。
これは、ブラウザがHTML、CSS、JavaScriptの実行順序を考慮し、動的にDOMツリーを構築・更新する過程で、CSSの適用ルールもリアルタイムに調整している証拠でもある。まるで、熟練の職人が現場の状況に合わせて臨機応変に作業を進めるように、ブラウザもまた、高度な判断を下しているわけだ。
実践!`:defined` を使ったサンプルコード
理論はわかった。だが、一番知りたいのは「どう使うのか?」だろう? ここからは、現場でそのまま使える、綺麗で実用的なサンプルコードをいくつか紹介しよう。
シナリオ1:カスタム要素定義前のフォールバックスタイル
カスタム要素がまだ定義されていない間は、ブラウザ標準の `inline-block` で表示し、最低限の見た目を確保したい場合。
カスタム要素のスタイル制御
これは通常の段落です。
これがカスタム要素です。
// script.js
// カスタム要素を定義するクラス
class CustomGreeting extends HTMLElement {
constructor() {
super();
// Shadow DOM を使う場合 (推奨)
const shadowRoot = this.attachShadow({ mode: ‘open’ });
const name = this.getAttribute(‘name’) || ‘User’;
shadowRoot.innerHTML = `
Hello, ${name}! (This is a Custom Element)
`;
}
}
// カスタム要素をブラウザに登録する
// この行が実行されると、ブラウザは ‘custom-greeting’ を定義済みと認識する
customElements.define(‘custom-greeting’, CustomGreeting);
console.log(‘custom-greeting が定義されました!’);
解説:
1. まず、`custom-greeting` に `:defined` が付いていないスタイルが適用されます。これは、JavaScriptで`customElements.define()`が実行される前の、ブラウザが「まだ知らない」状態を想定したスタイルです。ここでは、`inline-block`で最低限の見た目を整えています。
2. 次に、`custom-greeting:defined` のスタイルが登場します。このスタイルは、`customElements.define()`によって`custom-greeting`が正式に定義された後に適用されます。ここでは、よりリッチなボーダーや背景色、フォントスタイルなどを指定しています。
3. `script.js`が読み込まれ、`customElements.define()`が実行されると、ブラウザはそのカスタム要素を「定義済み」と認識し、`:defined`セレクタにマッチするようになります。これにより、スタイルが自動的に切り替わるわけです。
4. `script.js` 内の `CustomGreeting` クラスの `:host` セレクタは、Shadow DOM内でカスタム要素自体にスタイルを適用するためのものです。ここでは、定義後のカスタム要素の基本的な見た目を設定していますが、外部CSSの`:defined`セレクタが優先される場合もあります(CSSの仕様やブラウザの実装によります)。
この例のように、`:defined` を使うことで、カスタム要素の読み込み・定義完了を待ってから、より洗練されたスタイルを適用するという、ユーザー体験を損なわないための柔軟な制御が可能になります。
シナリオ2:カスタム要素が定義されていない場合の「ローディング状態」表示
カスタム要素がまだ読み込まれていない、あるいは定義に時間がかかっている場合に、「読み込み中…」といったインジケーターを表示したい場合。
カスタム要素のローディング表示
Loading widget…
これはカスタム要素の後に続く通常の段落です。
// widget-script.js
// カスタム要素を定義するクラス
class CustomWidget extends HTMLElement {
constructor() {
super();
// Shadow DOM を初期化
const shadowRoot = this.attachShadow({ mode: ‘open’ });
// ローディング表示要素を Shadow DOM に追加
const loadingIndicator = document.createElement(‘div’);
loadingIndicator.setAttribute(‘class’, ‘loading-indicator’);
loadingIndicator.innerHTML = `
Loading widget…
`;
shadowRoot.appendChild(loadingIndicator);
// 実際のウィジェットコンテンツを準備(ここではプレースホルダー)
const widgetContent = document.createElement(‘div’);
widgetContent.setAttribute(‘class’, ‘widget-content’); // 定義後に表示されるコンテンツ
const dataLabel = this.getAttribute(‘data-label’) || ‘Default Label’;
widgetContent.innerHTML = `
${dataLabel}
Widget loaded successfully!
`;
shadowRoot.appendChild(widgetContent);
// 実際のカスタム要素のコンテンツを Shadow DOM に設定する
// この関数は、カスタム要素が定義された後に呼び出される
this.renderWidgetContent = (label) => {
const content = shadowRoot.querySelector(‘.widget-content’);
if (content) {
content.querySelector(‘h2’).textContent = label;
}
};
}
// カスタム要素が DOM に追加されたときに呼び出されるライフサイクルコールバック
connectedCallback() {
// ここで非同期処理(API呼び出しなど)を行い、
// 完了後に renderWidgetContent を呼び出すことも可能
const dataLabel = this.getAttribute(‘data-label’) || ‘Default Label’;
setTimeout(() => { // 非同期処理のシミュレーション
this.renderWidgetContent(dataLabel);
// console.log(‘Widget content rendered.’);
}, 1500); // 1.5秒後にコンテンツを表示
}
}
// カスタム要素をブラウザに登録する
// この行が実行されると、ブラウザは ‘custom-widget’ を定義済みと認識する
customElements.define(‘custom-widget’, CustomWidget);
console.log(‘custom-widget が定義されました!’);
解説:
1. まず、HTML側で `
2. CSSでは、`custom-widget` のデフォルトスタイルを定義します。そして、`:not(:defined)`(あるいは単に `:defined` が付いていない状態)を想定したスタイルで、「Loading widget…」のようなローディング表示の見た目を指定します。
3. JavaScript (`widget-script.js`) では、`CustomWidget` クラスを定義し、`customElements.define()`で登録します。
4. 重要なポイント: この例では、ローディング表示をカスタム要素の Shadow DOM 内 で管理しています。
- `constructor` 内でローディング表示用の `div` (`.loading-indicator`) を Shadow DOM に追加します。
- そして、`custom-widget:defined .loading-indicator` というセレクタ(Shadow DOM 内のスタイルとして記述)を使って、カスタム要素が定義されたら `.loading-indicator` を `display: none;` にして隠しています。
5. `connectedCallback` では、非同期処理(ここでは `setTimeout` でシミュレート)を行い、完了後に実際のウィジェットコンテンツを表示するようにしています。この非同期処理が終わるまで、ローディング表示がユーザーに見え続けている状態です。
このように`:defined`疑似クラスをShadow DOM内のスタイルと組み合わせることで、カスタム要素の定義完了と同時に、ローディング表示をスムーズに消し、本来のコンテンツを表示させることができます。
まとめ:`:defined` はWeb Components開発の必須スキル
さて、今日は`:defined`疑似クラスについて、その正体からブラウザの内部処理、そして実践的なコード例まで、じっくりと見てきた。
- `:defined` は、カスタム要素がブラウザに定義済みかどうかをCSSで選択するための強力なツールだ。
- Web Componentsの読み込み前後でスタイルを制御し、ユーザー体験を向上させるために不可欠な存在となる。
- ブラウザはJavaScriptによる`customElements.define()`の実行を検知し、`:defined`セレクタのマッチングを動的に行う。
- フォールバックスタイルの適用や、ローディング状態の表示・非表示など、様々なシナリオで活用できる。
Web Componentsは、コンポーネントベース開発の強力な選択肢であり、そのエコシステムは日々進化している。`:defined`疑似クラスを使いこなすことは、モダンなフロントエンド開発において、ますます重要になってくるだろう。
今回紹介したサンプルコードを参考に、ぜひ君たちのプロジェクトでも`:defined`疑似クラスを試してみてほしい。きっと、これまで以上に柔軟で、洗練されたUIが実現できるはずだ。
何か不明な点があれば、いつでも聞きに来てくれ。現場で培った経験を惜しみなく共有するからな! それじゃ、また次のセッションで会おう!

コメント