CSS Shadow Parts は、開発者がシャドウツリー内の要素に CSS プロパティをスタイル設定することを可能にします。これは、Ionic Framework Shadow DOMコンポーネントをカスタマイズする際に非常に便利です。
Ionic Framework は、Web Componentsの分散型セットです。Web Components は、スタイルとマークアップをカプセル化するためにShadow DOM の仕様に従っています。
Shadow DOM は、スタイルがコンポーネントから漏れて、意図せずに他の要素に適用されるのを防ぐのに便利です。例えば、ion-button
コンポーネントに .button
クラスを割り当てています。Shadow DOM によるカプセル化がなければ、ユーザーが自分の要素に .button
クラスを設定した場合、Ionic Framework のボタンスタイルを継承してしまうでしょう。ion-button` は Shadow コンポーネントであるため、これは問題ではありません。
しかし、このカプセル化のために、スタイルは Shadow コンポーネントの内部要素に侵入することができません。つまり、Shadow コンポーネントがそのシャドウツリーの内部にある要素をレンダリングする場合、その内部要素を CSS で直接ターゲットにすることはできません。例として ion-select
コンポーネントを使用すると、次のようなマークアップがレンダリングされます。
<ion-select>
#shadow-root
<div class="select-text select-placeholder"></div>
<div class="select-icon"></div>
</ion-select>
プレースホルダーのテキストとアイコン要素は #shadow-root
の内部にあるため、以下の CSS はプレースホルダーのスタイル付けに 無効 です。
ion-select .select-placeholder {
color: blue;
}
So how do we solve this? CSS Shadow Parts!
Shadow Parts は、開発者がシャドウツリーの外側から、シャドウツリー内のスタイルを設定することを可能にします。これを行うには、part を公開し 、::part を使用してスタイルを設定する必要があります。
シャドウ DOM コンポーネントを作成する際、シャドウツリー内の要素に part
属性を割り当てることで、パートを追加することができます。これは Ionic Framework でコンポーネントに追加され、エンドユーザーからのアクションは必要ありません。
引き続き、例として ion-select
コンポーネントを使用し、マークアップは以下のように更新されます。
<ion-select>
#shadow-root
<div part="placeholder" class="select-text select-placeholder"></div>
<div part="icon" class="select-icon"></div>
</ion-select>
上記では、placeholder
とicon
という 2 つの Parts を表示しています。すべての Parts については、select documentation を参照してください。
これらの Parts が公開されたことで、要素は ::part を使って直接スタイルを設定することができるようになりました。
`::part()` 擬似要素により、開発者はPart属性で公開されているシャドウツリー内の要素を選択することができます。
ion-select
は、値が選択されていないときにテキストをスタイル付けするための placeholder
Part を公開していることが分かっているので、次のようにカスタマイズすることができます。
ion-select::part(placeholder) {
color: blue;
opacity: 1;
}
part
を使ったスタイリングでは、その要素で受け付けられる任意の CSS プロパティを変更することができます。
part をターゲットにできることに加え、擬似要素を明示的に露出させずにスタイル付けすることができます。
ion-select::part(placeholder)::first-letter {
font-size: 22px;
font-weight: 500;
}
Parts は、ほとんどの擬似クラスでも動作します。
ion-item::part(native):hover {
color: green;
}
Ionic Framework コンポーネントのすべての公開 Parts は、その API ページの「CSS Shadow Parts」の見出しで確認できます。すべてのコンポーネントとその API ページを表示するには、Component documentation を参照してください。
コンポーネントが Parts を持つためには、以下の条件を満たしている必要があります。
- Shadow DOM コンポーネントであること。 Scoped または Light DOM コンポーネントの場合、子要素を直接対象とすることができる。コンポーネントが Scoped または Shadow の場合、コンポーネントのドキュメントページ にその名前で表示されます。
- これは子要素を含んでいます。例えば、
ion-card-header
は Shadow コンポーネントですが、すべてのスタイルはホストエレメントに適用されます。子要素を持たないので、Parts は必要ありません。 - 子要素は構造的なものではありません。
ion-title
を含む特定のコンポーネントでは、子要素は内部要素を配置するために使用される構造的な要素です。構造的な要素をカスタマイズすることは、予期しない結果をもたらす可能性があるため、お勧めしません。
CSS Shadow Parts は最近のすべてのメジャーブラウザでサポートされています。ただし、一部の古いバージョンでは shadow parts がサポートされていません。アプリに parts を実装する前に、ブラウザのサポートが要件を満たしていることを確認してください。旧バージョンのブラウザのサポートが必要な場合は、引き続き CSS Variables を使用してスタイリングすることをお勧めします。
Vendor prefixed擬似要素は現時点ではサポートされていません。この例としては、 `::-webkit-scrollbar` 擬似要素があります: pseudo-elements:
my-component::part(scroll)::-webkit-scrollbar {
background: green;
}
詳しくは GitHub の Issue をご覧ください。
ほとんどの擬似クラスは Parts でサポートされていますが、構造的な擬似クラスはサポートされていません。動作しない構造的擬似クラスの例を以下に示します。
my-component::part(container):first-child {
background: green;
}
my-component::part(container):last-child {
background: green;
}
擬似要素 ::part()
は追加の ::part()
にマッチすることができません。
例えば、my-component::part(button)::part(label)
は何もマッチしません。これは、そうすることで意図した以上の構造的な情報を露出してしまうからです。
もし <my-component>
の内部ボタンが part="label => button-label"
のようなものを使って、ボタンの内部 Parts をパネル自身の part 要素マップに転送していた場合、 my-component::part(button-label)
といったセレクタはボタンのラベルだけを選び、他のラベルを無視することになるでしょう。