【テクニカル・上級編】 編集可能擬似クラス :read-write – CSS実践ガイド

CSSの進化は止まらない。かつてはJavaScriptがDOMのステータスを監視し、ちまちまとクラス名を付け替えて実現していたスタイリングが、今やネイティブのセレクタ一つでエレガントに解決する時代だ。

今回スポットを当てるのは、編集可能な要素を捉える `:read-write` 擬似クラスだ。
「なんだ、`contenteditable` や `` にスタイルを当てるだけの地味な奴か」と思ったそこのあなた。その認識のままでは、モダンなWebアプリケーションのパフォーマンス最適化や、複雑なフォーム設計の現場で痛い目を見る。ブラウザのレンダリングパイプラインとメモリ効率、そして非同期フレームワークの闇を知るシニアエンジニアの視点から、この `:read-write` の深淵を覗いてみよう。

—

1. `:read-write` とは何か:仕様の裏側とブラウザの挙動

まずは基本の復習だが、ただのおさらいで終わらせない。`:read-write` 擬似クラスは、ユーザーが編集可能な要素にマッチする。具体的には以下の要素が対象となる。

  • `input` 要素(`readonly` や `disabled` が付与されていないもの)
  • `textarea` 要素(同上)
  • `contenteditable` 属性が付与されたあらゆる要素(`div` や `section` など)

ここでブラウザエンジンの内部挙動に思いを馳せてほしい。
DOMツリーが構築され、スタイル計算(Style Recalculation)が走る際、ブラウザは要素の「状態(State)」を評価する。`:read-write` は動的な状態擬似クラス(Dynamic Pseudo-class)に分類される。つまり、ユーザーの入力やJSによる属性の書き換えによって、リアルタイムにマッチングが変動する。

安易にグローバルなセレクタで `:read-write` を乱用するとどうなるか?
DOMの規模が数千、数万に膨れ上がった巨大なSPA(シングルページアプリケーション)において、動的擬似クラスの再評価は、スタイリングの再計算コスト(レイアウト・スラッシングの温床)を直撃する。特に `contenteditable` を多用するリッチテキストエディタなどを実装する場合、セレクタの設計を誤ると、タイピングのたびにメインスレッドがブロックされるという悪夢を見る事になる。

2. 実務で直面する「非同期の競合」とリアクティブフレームワークの罠

現代の開発現場では、React、Vue、Svelteといったリアクティブフレームワーク全盛だ。ここでよくあるバグのユースケースを話そう。

「APIからデータを取得し、フォームを非同期で描画する。最初は読み取り専用(`readonly`)だったが、権限チェックの非同期処理が終わった瞬間に編集可能(`read-write`)に切り替える」

この時、フレームワークの仮想DOMの差分検出と、ブラウザの実際のDOMプロパティの書き換え、そしてCSSOMの適用タイミングにわずかなズレが生じることがある。特に、親要素に `:read-write` を伝播させようとした際、JSのステートとCSSのセレクタ評価がデカップリングを起こし、一瞬だけスタイルの適用漏れや、意図しないスタイルのフリッカー(チラつき)が発生する。

この問題を回避するための堅牢なアーキテクチャの鉄則は、「状態の真実のソース(Single Source of Truth)をCSSにも正しく同期させること」だ。

/
NGな例: 複雑すぎるネストや、フレームワークの非同期描画タイミングに
依存したセレクタは、スタイル計算のキャッシュ効率を悪化させる
/
.form-container div[contenteditable=”true”]:read-write {
/ …重いスタイル… /
}

/
OKな例: 状態を明確化し、GPUレイヤーの無駄な生成を防ぐミニマルな設計
/
.app-input {
background-color: var(–color-bg-readonly);
border: 1px solid transparent;
transition: background-color 0.2s ease, border-color 0.2s ease;
}

/ 編集可能になった瞬間のみ、コストの低いプロパティ(色やボーダー)を変化させる /
.app-input:read-write {
background-color: var(–color-bg-editable);
border-color: var(–color-primary);
/ ユーザーのタイピング体験を阻害しないよう、重いbox-shadowなどは最小限に /
box-shadow: 0 0 0 2px var(–color-primary-alpha);
}

3. メモリ効率とレンダリング最適化の極意

CSSアーキテクトとして最も強調したいのは、「メモリとペイント負荷の最適化」だ。

`:read-write` を使う最大のメリットは、JavaScriptで `is-editing` のようなクラスを要素にちまちまトグルする処理を完全に排除できる点にある。JSのイベントリスナーやクラス操作のコード量が減るため、JSヒープメモリの消費を抑えられ、ガベージコレクション(GC)の走る頻度を下げることができる。これは特にモバイル環境やローエンド端末において、アプリの寿命(バッテリー消費)に直結する重要なファクターだ。

しかし、セレクタの書き方次第でその恩恵は吹き飛ぶ。以下のコードを見てほしい。

/ ————————————————————————–
【パフォーマンス・アンチパターン】
ユニバーサルセレクタや深すぎる子孫セレクタとの組み合わせは厳禁
————————————————————————– /
.dashboard-view :read-write {
outline: 2px solid blue;
}

/ ————————————————————————–
【プロフェッショナル・パターン】
スコープを限定し、クラスベースのハイブリッド設計にする
————————————————————————– /
.control-field:read-write {
/
ブラウザに「この要素は動的に変化する」ことを効率よく伝えつつ、
ペイント領域を限定(containment)するモダンCSSの極意
/
contain: layout style paint;
outline: none;
}

.control-field:read-write:focus-visible {
border-color: var(–focus-ring-color);
box-shadow: var(–focus-shadow);
}

ここで紹介した `contain: layout style paint;` は、CSS Containment Moduleの強力な機能だ。`:read-write` によって動的にスタイルやレイアウトが変わる可能性のあるカスタムエディタや入力フィールドに対し、ブラウザの再計算スコープをその要素単体に閉じ込める。これにより、DOMツリー全体へのスタイル再計算の波及を防ぎ、レンダリングのフレームレート(60fps / 120fps)を確実に死守できる。

4. 重大なバグの回避策:フォーカス管理との共存

実務で `:read-write` を扱う際、避けて通れないのが「フォーカス時(`:focus` や `:focus-visible`)との競合および仕様の差異」だ。

`:read-write` は「編集可能であること」を指すが、ユーザーが「今まさにそこにフォーカスしているか」は別問題である。よくあるバグとして、読み取り専用から編集可能に変わった瞬間、ユーザーの意図しないハイライトが全面に出てしまい、UIがガチャガチャして見えるというものがある。

これを防ぐためには、状態の階層を明確に分離し、CSSの特異性(Specificity)をコントロールする必要がある。

ここにテキストを入力してください…

/ 1. ベースの状態(静的) /
.control-field {
padding: 0.75rem 1rem;
background-color: #f8fafc;
border: 1px solid #cbd5e1;
color: #64748b;
cursor: default;
transition: all 0.15s ease-in-out;
}

/ 2. 編集可能になった状態(動的: :read-write) /
.control-field:read-write {
background-color: #ffffff;
color: #0f172a;
cursor: text;
}

/ 3. 編集可能かつ、現在フォーカスされている状態(複合条件) /
.control-field:read-write:focus-visible {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
}

/ 4. エラー状態(JS等で付与されるクラスとの調停) /
.control-field:read-write.is-error {
border-color: #ef4444;
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.2);
}

このアプローチの美しいところは、JS側で「編集可能になったから `is-editable` クラスを貼る」という冗長なコードを書く必要が一切ない点だ。属性や要素のセマンティクス(`contenteditable` や `readonly` の有無)をCSSが直接監視し、ブラウザエンジンレベルで最適化されたスタイリングを適用する。

結び:ギークなCSSエンジニアリングの美学

CSSの擬似クラスを使いこなすということは、ブラウザの内部エンジン(Blink, Gecko, WebKit)と対話することに他ならない。

`:read-write` は、単にマークアップの手間を減らすためのショートカットではない。JavaScriptの介在を最小限に抑え、メモリ効率を高め、レンダリングの負荷を極限まで削ぎ落とすための、極めてアーキテクチャ寄りの武器なのだ。

公式マニュアルのサンプルコードをコピペして満足する日々は終わりにしよう。ブラウザの挙動を愛し、メモリとパフォーマンスの限界に挑むコードこそが、真に堅牢なWebアプリケーションを支えるのだから。さあ、今すぐ君のコードベースにある無駄なJS製クラス切り替えロジックを削除し、ネイティブの力に置き換えに行こう。

コメント

タイトルとURLをコピーしました