Astroで画像のホバーCSSが効かなかった原因
このブログを直している途中で、「ホバー時の画像のCSS変えた?」と聞いたことがある。
記事カードの画像は、普段は少し色を落とし、マウスを重ねると元の色に戻るようにしている。その表示を直した履歴を読むと、変更したのはCSSではなく、画像コンポーネントの属性の受け渡しだった。

画像:CSS-Nextの公式ロゴ。ロゴの色と形を保ち、掲載用に余白を付けています。
画像を表示する部品を挟んでいた
カード内の画像は、AstroのImageを直接置く代わりに、PostImage.astroを通している。
ControlPanelCard.astro
└─ PostImage.astro
└─ Image → imgこの部品には、縦長の画像を一定の高さに収める役割もある。本の表紙を最後まで見せるための調整を、使う場所ごとに書かずに済む。
一方、画像を灰色に近づける指定は、呼び出し元のカード側にある。通常はgrayscale(0.8)、ホバー時はgrayscale(0)。変化にかける時間は0.3秒だ。
画像自体は表示できていても、呼び出し元からスタイルが届くかは、別に確認する必要があった。
CSSの対象は、単なるimgではない
Astroのコンポーネント内に書いた通常のstyleは、そのコンポーネントの範囲に適用される。
このサイトの既定の方式では、生成されたHTMLへdata-astro-cid-…という属性が付き、CSS側にも対応する条件が加わる。説明用に短く書くと、次のような関係になる。
<img data-astro-cid-example />img[data-astro-cid-example] {
filter: grayscale(0.8);
}exampleは説明用の名前で、実際の識別子ではない。
この仕組みによって、ほかの場所の画像まで同じ指定が広がることを避けられる。ただ、コンポーネントを一つ挟んだときに、この属性を途中で落とすと、最終的なimgがCSSの条件に合わなくなる。
Astroの公式ドキュメントにも、既定のスコープ方式では、この属性を子へ渡す必要があると説明されている。
修正したのは、受け取った属性の行き先
修正前のPostImageは、Astro.propsから使う値だけを取り出していた。
const { src, alt, portraitMaxHeight = 128 } = Astro.props;修正後は、それ以外の属性も受け取る形になった。
const { src, alt, portraitMaxHeight = 128, ...attributes } = Astro.props;さらに、そのattributesを内側のImageへ渡している。実際のテンプレートからの抜粋は次のとおり。
<Image
{...attributes}
src={src}
alt={alt}
class:list={{ portrait: src.height > src.width }}
style={`--portrait-max-height: ${portraitMaxHeight}px`}
/>この変更は、9月17日の修正コミットに残っている。色やアニメーションの指定を変えたのではなく、親から受け取ったスコープ属性を画像へ届かせる変更だった。
なお、この部品はclassとstyleを自身でも指定している。今回の修正だけで、呼び出し元から来るあらゆるclassやstyleが自動的に合成されるわけではない。汎用的な画像部品へ広げるなら、その扱いも決めたい。
同じ「効かない」でも、確かめる場所が違う
今回のような症状なら、次の順に見ると原因を絞りやすい。
- CSSのルールが生成されているか。
- 画像のHTMLに、そのルールが必要とする属性があるか。
- 別のルールで上書きされていないか。
- ホバー用のメディア条件を満たしているか。
このサイトのホバー処理にはany-hover: hoverという条件がある。ホバーできる入力機器がない環境では、そのルール自体が適用されない。PCで属性を確認することと、タッチ操作で表示を見ることは分けて考える。
CSSの優先度を上げる前に、そもそも対象の要素が条件に合っているかを見る。今回の差分は、その確認の大切さを説明できる小さな例になった。
共通化した部品の、境目を見る
srcとaltが渡れば、画像は表示できる。でも、親のスタイルが届くためには、それ以外の属性も必要だった。
共通コンポーネントを作るときは、その部品が自分で使う値と、内側へ通す値を整理する。見た目の不具合でも、原因はCSSファイルの外にあることがある。
この修正はCodexと進めた。今回は修正履歴と現在のHTML・CSSを読み返し、どこで属性が止まっていたのかを整理した。