やあ。最近、Web Componentsやモダンなフレームワークを使った設計で頭を悩ませていないかい?
「カスタム要素を作ったはいいものの、JavaScriptの読み込み遅延やエラーで、レンダリングの瞬間に中身がただのカスタムタグの文字列として裸で露出してしまう……あのカッコ悪い瞬間を防ぎたいんだよね」
後輩くん、よくぞ聞いてくれた。その悩み、まさに現場のプロが一度は通る「FOUC(Flash of Unstyled Content:スタイル未適用のコンテンツの閃光)」という魔物との戦いだ。
今回は、その泥臭い問題に対してCSSの仕様側からスマートにカウンターを叩き込める、`:defined`擬似クラスについて徹底的に解説しよう。これを使えるようになると、フロントエンドのアーキテクチャとしての完成度が一段と引き締まるぞ。
—
1. そもそも `:defined` 擬似クラスとは何か?
`:defined` は、一言で言うと「ブラウザにそのカスタム要素の定義(`customElements.define()`)が登録されているかどうか」を判定するための擬似クラスだ。
実はこれ、カスタム要素(Custom Elements)だけでなく、標準のHTML要素(`
` など)に対しても常にマッチするという性質を持っている。
つまり、ブラウザのパーサーにとって「すでに知っている(定義済みの)要素」であれば、何であれ対象になるんだ。
なぜこれが実務で必要なのか?
現代のWeb開発では、巨大なJavaScriptバンドルを非同期で読み込んだり、遅延ロード(Lazy Loading)させたりすることが多いよね。
例えば、以下のようなカスタム要素を置いたとする。
JavaScriptの読み込みが完了し、`customElements.define(‘user-card’, UserCard)` が実行されるまでのわずか数十〜数百ミリ秒の間、ブラウザはこの `
ここに `:not(:defined)` を組み合わせることで、「まだ定義されていないカスタム要素」だけを狙い撃ちにしてスタイリングできるというわけだ。ここが実務における最大のキモになる。
—
2. ブラウザの裏側の動き:なぜFOUCが起きるのか
少し裏側の話をしよう。ブラウザがHTMLを上からパースしていくとき、未知のタグ(ハイフン `-` を含むタグなど)に出会うと、ブラウザはそれを「未定義のカスタム要素」として `HTMLElement` のフォールバック(無名のインライン要素のような扱い)でとりあえず画面に出そうとする。
そして、後から JavaScript が実行されて `customElements.define()` が呼ばれた瞬間に、ブラウザはその要素を「アップグレード(Upgrade)」する。
この「未定義から定義済みへの遷移」のタイムラグのせいで、デザインがガタつく。
これをCSSだけでコントロールできるのが、`:defined` の最高なところなんだ。JavaScriptのライフサイクルに完全に依存せず、宣言的にスタイリングをコントロールできる。これぞCSSアーキテクトの腕の見せ所だよ。
—
3. 実践!コピペで使えるプロダクションコード
百聞は一見にしかずだ。実務の現場でそのまま使える、骨太なコードを見ていこう。
今回は、未定義時にはローディングのスケルトンを表示し、定義が完了したらふわっとフェードインさせるコンポーネントの例だ。
HTML
マイアカウント情報
CSS (`style.css`)
/ ==========================================================
- 1. 未定義のカスタム要素に対するフォールバック(スケルトンUI)
- ========================================================== /
user-profile-card:not(:defined) {
/ ブロック要素として正しくレイアウトスペースを確保する /
display: block;
width: 100%;
max-width: 400px;
height: 120px;
/ ローディング中であることを示すグレーのプレースホルダー背景 /
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading-skeleton 1.5s infinite;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
/ スケルトン用のシマシマを動かすアニメーション /
@keyframes loading-skeleton {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
/ ==========================================================
- 2. 定義完了(Defined)後のスタイルとフェードイン演出
- ========================================================== /
user-profile-card:defined {
display: block;
opacity: 0;
/ 綺麗にパッと切り替わるのではなく、ふわっと現れさせる /
animation: fade-in 0.4s ease-out forwards;
}
@keyframes fade-in {
to {
opacity: 1;
}
}
JavaScript (`app.js`)
// モック用のカスタム要素クラス
class UserProfileCard extends HTMLElement {
constructor() {
super();
// シャドウDOMの構築
const shadow = this.attachShadow({ mode: ‘open’ });
shadow.innerHTML = `
シニアエンジニア 太郎
ID: ${this.getAttribute(‘user-id’) || ‘不明’} – 状態: アクティブ
`;
}
}
// ネットワーク遅延をシミュレートするためにあえて少し遅らせて定義する
setTimeout(() => {
customElements.define(‘user-profile-card’, UserProfileCard);
console.log(‘user-profile-card が定義されました!’);
}, 1200);
—
4. シニアから後輩への実践的なアドバイス・注意点
この `:defined` 擬似クラス、非常に強力なんだけど、現場で使うときにはいくつか注意すべきポイントがある。ここを抑えておかないと、思わぬバグに足元をすくわれるぞ。
① スコープとセレクタの詳しさ(特異性)に注意
`:not(:defined)` を使うときは、CSSの特異性(Specificity)に気をつけよう。あまりに複雑なセレクタの中に混ぜ込むと、後からカスタム要素内部のシャドウDOMにスタイルが上書きされて効かなくなることがある。基本的にはタグ名単体か、シンプルなクラスとの組み合わせで運用するのが吉だ。
② IE11? そんなものは過去の遺物だ
当然ながら、モダンブラウザの仕様だ。モダンなWeb Componentsの文脈で使うものだから、レガシーブラウザのことは綺麗さっぱり忘れよう。主要なモダンブラウザ(Chrome, Safari, Firefox, Edge)はすべて完璧にサポートしている。
③ レイアウトシフト(CLS)対策に直結する
Web Vitalsの指標の一つであるCLS(Cumulative Layout Shift)において、要素の読み込み時にガクッとレイアウトがズレるのは最悪のユーザー体験だ。`:not(:defined)` の段階であらかじめ `min-height` や幅・高さをキッチリ指定しておけば、カスタム要素がアップグレードされたときもレイアウトが暴れない。SEOやパフォーマンスの観点からも、これは非常に優秀なプラクティスなんだ。
—
まとめ
CSSの進化は早い。昔はJavaScriptのロード完了を待つために、不格好なクラスをJS側で付与したり、HTMLの初期化スクリプトでゴニョゴニョしたりしていた。しかし、今のCSSはブラウザのライフサイクルそのものに直接介入できる。
`:defined` は、Web Componentsを実務で本格的にスケールさせたいチームにとって、間違いなくマストで覚えておくべき隠し味のようなセレクタだ。
さあ、今日のコードを君のプロジェクトのローディング処理に組み込んで、チームをあっと言わせに行こうぜ!

コメント