CSSでホバー時にカードを持ち上げるには
カーソルを載せたカードを transform: translateY で少し上へ動かし、同時に box-shadow を広げて持ち上がったように見せる。
変化は transition で 0.2 秒かけて行い、タッチ端末・キーボード操作・動きを減らす設定にもそれぞれ対応させる。
実行確認済みは、ブラウザで実際に描画して見た目を確認したものである。
公開:
<div class="cards">
<a class="card" href="#">
<span class="card__title">カード A</span>
<span class="card__text">カーソルを載せると持ち上がる。</span>
</a>
<a class="card" href="#">
<span class="card__title">カード B</span>
<span class="card__text">Tab キーで選んでも同じ動きになる。</span>
</a>
</div>.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 16px;
max-width: 400px;
padding: 8px 0;
}
/* a は既定で inline なので、block にしないと transform が効かない */
.card {
display: block;
padding: 16px;
border: 1px solid #e4e4e7;
border-radius: 12px;
background: #fff;
color: #27272a;
text-decoration: none;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
/* transition は :hover 側ではなく通常時に書く(戻るときも動かすため) */
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.card__title {
display: block;
margin-bottom: 4px;
font-weight: 600;
}
.card__text {
font-size: 13px;
color: #52525b;
}
/* マウスのように hover できる端末でだけ持ち上げる */
@media (hover: hover) {
.card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.16);
}
}
/* キーボードで選んだときも同じ見た目にする */
.card:focus-visible {
outline: 2px solid #0f766e;
outline-offset: 2px;
transform: translateY(-4px);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.16);
}
/* 動きを減らす設定では移動をやめ、影の変化だけを残す */
@media (prefers-reduced-motion: reduce) {
.card {
transition: box-shadow 0.2s ease;
}
.card:hover,
.card:focus-visible {
transform: none;
}
}持ち上げる動きは transform: translateY で付け、影を大きく薄く広げて「面が手前に来た」ことを示す。
margin や top で動かさないのは、transform なら見た目だけが動いてレイアウトの計算に影響せず、隣のカードや下の行がずれないためである。
つまずき
動きがぎこちないときは、transition を :hover 側にだけ書いていないかを確認するとよい。
transition は変化した後のスタイルに書かれた値が使われるため、:hover 側にだけ書くと持ち上がるときはなめらかに動くのに、カーソルを外したときは一瞬で元に戻る。
通常時のセレクタに書けば行きも帰りも同じ速さで動く。
もう 1 つの典型は、a や span に transform を指定しても何も起きないというものである。
transform はインライン要素には効かないので、上の例のように display: block か inline-block を指定する必要がある。
タッチ端末では hover を前提にしない
スマートフォンのようにタッチ操作が主の端末は、カーソルを載せるという操作そのものを持たない。
端末によってはタップや長押しで hover を模倣するため、タップしたカードが持ち上がったまま残ることがある。
上の例では持ち上げる指定を @media (hover: hover) の中に入れ、主な入力手段が無理なく hover できる端末でだけ動かしている。
hover でしか分からない情報(説明文やボタン)を隠しておく作りにすると、タッチ端末ではその情報に届かなくなるので避けたい。
キーボード操作でも同じ反応にする
カードがリンクであれば、Tab キーでフォーカスを当てて選ぶ利用者もいる。
hover にしか指定がないと、キーボードで選んだときは見た目が何も変わらず、どのカードを選んでいるのか分からない。
上の例では :focus-visible にも同じ transform と box-shadow を書き、あわせて outline でフォーカスの位置をはっきり示している。
:focus ではなく :focus-visible を使うのは、マウスでクリックしたときにまで枠を出さないためである。
動きを減らしたい利用者への配慮
画面上の要素が動くことで不快を感じる利用者は、OS の設定で視差効果や動きを減らすように指定していることがある。
その指定は prefers-reduced-motion: reduce のメディアクエリで受け取れる。
上の例ではこの条件のときに translateY をやめ、transition も box-shadow だけに絞っている。
影の変化は残しているので、どのカードが選ばれているかという情報は失われない。
動かすプロパティを絞る理由
transform は描画の最終段階(合成)だけで処理できるため、アニメーションしても負荷が小さい。
一方で box-shadow の変化は描画のやり直しを伴うので、同時に動かす要素が多いと重くなることがある。
カードが数枚から十数枚程度なら上の書き方で問題になることは少ないが、transition: all のように全プロパティを対象にすると、意図しないプロパティまで動いて、負荷が増えたり見た目が崩れたりすることがある。
動かすプロパティは上の例のように名前で列挙するのが安全である。