CSS 選択のスタイル設定の継承の変更

公開日: 2024 年 10 月 8 日

Chrome 131 より、::selection 疑似クラスと ::target-text 疑似クラスの CSS ハイライトの継承が変更されます。これは、最近追加された ::highlight::spelling-error::grammar-error 疑似クラスに合わせて、より直感的な継承モデルを作成するためです。この投稿では、この変更について解説しますが、ほとんどのサイトにとって目に見える影響はありません。

選択範囲のスタイル設定

選択したテキストの外観をスタイル設定することで、選択したコンテンツの目的や、テキストをまったく選択できないことなどの意味をユーザーに伝えることができます。たとえば、GitHub では、選択されたディレクトリ構造とは異なる色が選択されます。

CSS では、::selection 疑似要素を使った選択スタイルがサポートされています。これは、ハイライト擬似要素と呼ばれる疑似要素のセットの一つです。これらの疑似要素は、さまざまなユーザー、ブラウザ、スクリプトによるアクションでテキストが表示される方法を制御します。選択以外にも、スペルミス(::spelling-error)、文法ミス(::grammar-error)、URL 埋め込みテキスト ターゲット(::target-text)、スクリプト生成ハイライト(::highlight)のスタイルを設定できます。

CSS プロパティのコレクションと同様に、サイトを設計する際は継承動作が重要な考慮事項になります。一般的に、デベロッパーは CSS プロパティは DOM 要素ツリーから継承される(例: font)か、まったく継承されない(例: background)ことを想定しています。

選択動作の変更(Chrome 131)

次のドキュメント フラグメントについて考えてみましょう。

p {
  color: red;
}

.blue::selection {
  color: blue;
}
<p class="blue">Some <em>emphasized</em> text that one would expect to be blue</p>

フラグメントのスタイル宣言は、選択したテキストの色を変更します。1 つのルールはすべての要素に一致し、もう 1 つのルールはクラス "blue" に一致します。Chrome 130 以前で選択した場合、次の結果になります。

青色と想定されるテキストが赤色になっている。

Chrome 131 でこれを選択すると、結果は次のようになります。

テキストが青色でハイライト表示されます。

変更点として、選択プロパティの継承動作は、これまでは元の要素の継承によって実装されていました。この場合、選択は、選択された要素に一致する ::selection のプロパティを使用します。Chrome バージョン 130 以前では、このモデルが使用されます。このモデルでは、.blue::selection"blue" クラスの要素のみと一致するため、強調表示されたテキストに一致する ::selection はありません。<em> 要素には "blue" クラスがありません。

Chrome 131 では、要素が親から選択動作を継承する新しい動作が可能になります。上の例では、<em> 要素には自身と一致する ::selection がないため、<p> 要素の選択色を継承します。これは CSS ハイライトの継承と呼ばれ、chrome://flags試験運用版のウェブ プラットフォームの機能を有効にすることで、以前の Chrome バージョンで試すことができます。

選択プロパティの継承に依存しているサイトでは、選択したテキストの外観が変更される可能性がありますが、バグレポートから得られた証拠によると、このような動作のユースケースはほとんどありません。

選択用の CSS カスタム プロパティは引き続き機能する

多くのサイトでは、CSS カスタム プロパティを使用して CSS ハイライトの継承をシミュレートしています。カスタム プロパティは要素ツリーから継承され、「親から継承」の結果は次のようなコード スニペットで示します。

:root {
   --selection-color: lightgreen;
}

::selection {
  color: var(--selection-color);
}

.blue {
  --selection-color: blue;
}
<p>Some <em>emphasized</em> text</p>
<p class="blue">Some <em>emphasized</em> text that is blue</p>

Chrome 130 と 131 の両方で選択した場合の結果は次のようになります。

最初の行は緑色、2 番目の行は青色です。

ここでは、すべての要素が要素ツリーを介して --selection-color プロパティの値を継承し、この色がテキストの選択時に使用されます。.blue クラスの要素とその子孫は選択されると青色になり、その他の要素は薄い緑色になります。多くのサイトでこの手法が使用されており、Stack Overflow で推奨される手法です。

互換性を維持するため、CSS ハイライト継承モデルでは、::selection(およびその他の CSS ハイライト疑似要素)が元の要素(適用先の要素)からカスタム プロパティ値を継承するように指定されています。この方法を使用しているサイトは、Chrome 131 の変更の影響を受けません。

継承動作の競合を回避するため、::selection 疑似要素自体で定義されたカスタム プロパティは無視されます。要素自体でプロパティを定義し、疑似要素で参照する必要があります。

::selection のユニバーサル セレクタでハイライトの継承を無効化

CSS カスタム プロパティを使用していないサイトでは、ユニバーサル セレクタを使用して選択したテキストの色を設定していた可能性があります。たとえば、次の CSS のようにします。

::selection /* = *::selection (universal) */ {
  color: lightgreen;
}

.blue::selection {
  color: blue;
}
<p>Some <em>emphasized</em> text</p>
<p class="blue">Some <em>emphasized</em> text</p>

Chrome 130 以前と Chrome 131 以降の両方で選択した場合の結果は次のようになります。

テキストの 1 行目は緑色です。2 つ目は青色ですが、強調された単語は緑色です。

CSS ハイライトの継承では、2 番目の強調テキストが親から青色を継承することはありません。これは、ユニバーサル セレクタが <em> 要素と一致し、ユニバーサル ハイライト色(ライトグリーン)が適用されるためです。

CSS ハイライトの継承のメリットを享受するには、ユニバーサル セレクタを変更してルートのみと一致するようにします。このルートは、その子孫に継承されます。

:root::selection {
  color: lightgreen;
}

.blue::selection {
  color: blue;
}
<p>Some <em>emphasized</em> text</p>
<p class="blue">Some <em>emphasized</em> text</p>

Chrome 131 での結果は次のようになります。

最初の行のテキストは緑色です。2 行目は青色です。

サイトで選択色を変更しているが、カスタム プロパティを使用していない場合、::selection 疑似用のユニバーサル セレクタが存在する可能性があります。幸い、Chrome のこの変更によってサイトが破損することはありません。ただし、ハイライトの継承による人間工学的なメリットは失われます。

::target-text のスタイルも変更されます

ここで説明する動作と変更はすべて、::selection と同様に ::target-text 疑似要素にも適用されます。1 つのサイトで複数のターゲット テキストのスタイル設定を使用するユースケースは限られており、この機能は非常に新しいため、サイトの ::target-text の動作が変更される可能性はほとんどありません。

変更の理由

他のハイライト疑似要素の開発中、CSS ワーキング グループは、ハイライト継承モデルを使用して継承を実装することを決定しました。これは、::selection 疑似要素の仕様ですでに定義されていた方法ですが、ブラウザでは実装されていませんでした。非選択疑似要素はハイライト継承を使用します。つまり、疑似要素はプロパティであるかのように継承されます。つまり、要素はドキュメントの親からハイライト疑似要素を継承します。

すべてのハイライト疑似の一貫性を保つため、CSS ワーキング グループは、::selection のハイライト継承のサポートを繰り返しました。ブラウザは、既存のサイトを壊さないよう、新しい動作のリリースに取り組んでいます。

試してみる

次の CodePen は変更を示しています。Chrome 131 で試してみてください。