ホバー
cssだけで作る、ホバーに連動して扇状に広がるカードレイアウト
2026/02/04
2026/2/4
複数のカードを並べるUIでは、「どれに注目しているのか」を自然に伝えることが重要です。
htmlとcssだけを使い、カードにホバーすると周囲のカードが連動して動く、扇状に広がるカードレイアウトを実装しました。
一枚だけが動くのではなく、全体が関係性を保ったまま反応することで、視線誘導と心地よい操作感を両立しています。
Preview プレビュー
Code コード
<div class="kumonosu-container">
<div class="kumonosu-card"><img src="https://kumonosu.net/wp-content/uploads/2026/01/176a.jpg.webp" alt="Person 1" class="kumonosu-image"></div>
<div class="kumonosu-card"><img src="https://kumonosu.net/wp-content/uploads/2026/01/176b.jpg.webp" alt="Person 2" class="kumonosu-image"></div>
<div class="kumonosu-card"><img src="https://kumonosu.net/wp-content/uploads/2026/01/176c.jpg.webp" alt="Person 3" class="kumonosu-image"></div>
<div class="kumonosu-card"><img src="https://kumonosu.net/wp-content/uploads/2026/01/176d.jpg.webp" alt="Person 4" class="kumonosu-image"></div>
<div class="kumonosu-card"><img src="https://kumonosu.net/wp-content/uploads/2026/01/176e.jpg.webp" alt="Person 5" class="kumonosu-image"></div>
<div class="kumonosu-card"><img src="https://kumonosu.net/wp-content/uploads/2026/01/176f.jpg.webp" alt="Person 6" class="kumonosu-image"></div>
<div class="kumonosu-card"><img src="https://kumonosu.net/wp-content/uploads/2026/01/176g.jpg.webp" alt="Person 7" class="kumonosu-image"></div>
</div>
/* --- 1. 基本設定 --- */
* {
box-sizing: border-box;
}
:root {
color-scheme: light dark;
--bg-dark: rgb(16, 24, 40);
--bg-light: rgb(248, 244, 238);
--txt-light: rgb(10, 10, 10);
--txt-dark: rgb(245, 245, 245);
--clr-bg: light-dark(var(--bg-light), var(--bg-dark));
--clr-txt: light-dark(var(--txt-light), var(--txt-dark));
}
body {
background-color: var(--clr-bg);
color: var(--clr-txt);
min-height: 100svh;
margin: 0;
padding: 2rem;
font-family: system-ui, -apple-system, sans-serif;
display: grid;
place-items: center;
overflow: hidden;
}
/* --- 2. メインコンテナ設定 --- */
.kumonosu-container {
--card-trans-duration: 1000ms;
--card-trans-easing: linear(0, 0.01 0.8%, 0.038 1.6%, 0.154 3.4%, 0.781 9.7%, 1.01 12.5%, 1.089 13.8%, 1.153 15.2%, 1.195 16.6%, 1.219 18%, 1.224 19.7%, 1.208 21.6%, 1.172 23.6%, 1.057 28.6%, 1.007 31.2%, 0.969 34.1%, 0.951 37.1%, 0.953 40.9%, 0.998 50.4%, 1.011 56%, 0.998 74.7%, 1);
--card-border-radius: 10px;
--card-width: 34vmin;
--radius: 70vmin;
--cards: 7;
--arc-size: 0.25;
--arc-center: 0.75;
--arc-start: calc(var(--arc-center) - var(--arc-size) / 2);
--arc-shift: 0;
--arc-shift-delta: 0.01;
position: relative;
width: var(--card-width);
aspect-ratio: 2/4.5;
}
@supports (order:sibling-index()) {
.kumonosu-container {
--cards: sibling-count();
}
}
/* --- 3. カード要素の設定 --- */
.kumonosu-card {
--card-i: 1;
--arc-step: calc(var(--arc-size) / (var(--cards) - 1));
position: absolute;
width: var(--card-width);
aspect-ratio: 4/6;
background: white;
border-radius: var(--card-border-radius);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
offset-path: circle(var(--radius) at 50% 100%);
offset-distance: calc((var(--arc-start) + (var(--card-i) - 1) * var(--arc-step) + var(--arc-shift)) * 100%);
offset-rotate: auto;
offset-anchor: 50% 0%;
transition: all var(--card-trans-duration) var(--card-trans-easing);
}
/* 各カードのインデックス(番号)振り */
.kumonosu-card:nth-child(1) {
--card-i: 1;
}
.kumonosu-card:nth-child(2) {
--card-i: 2;
}
.kumonosu-card:nth-child(3) {
--card-i: 3;
}
.kumonosu-card:nth-child(4) {
--card-i: 4;
}
.kumonosu-card:nth-child(5) {
--card-i: 5;
}
.kumonosu-card:nth-child(6) {
--card-i: 6;
}
.kumonosu-card:nth-child(7) {
--card-i: 7;
}
@supports (order:sibling-index()) {
.kumonosu-card {
--card-i: sibling-index();
}
}
/* 重なり順の固定設定 */
.kumonosu-card:where(:nth-child(1), :nth-child(7)) {
z-index: 0;
}
.kumonosu-card:where(:nth-child(2), :nth-child(6)) {
z-index: 1;
}
.kumonosu-card:where(:nth-child(3), :nth-child(5)) {
z-index: 3;
}
.kumonosu-card:nth-child(4) {
z-index: 4;
}
/* --- 4. ホバーアクション --- */
.kumonosu-card:hover {
offset-anchor: 50% 10%;
}
/* ホバーされた要素の右隣(弟要素)をずらす */
.kumonosu-card:hover+.kumonosu-card {
--arc-shift: calc(var(--arc-shift-delta) * 3);
}
.kumonosu-card:hover+.kumonosu-card+.kumonosu-card {
--arc-shift: calc(var(--arc-shift-delta) * 2);
}
.kumonosu-card:hover+.kumonosu-card+.kumonosu-card+.kumonosu-card {
--arc-shift: calc(var(--arc-shift-delta) * 1);
}
/* ホバーされた要素の左隣(兄要素)をずらす */
.kumonosu-card:has(+ .kumonosu-card:hover) {
--arc-shift: calc(var(--arc-shift-delta) * -3);
}
.kumonosu-card:has(+ .kumonosu-card + .kumonosu-card:hover) {
--arc-shift: calc(var(--arc-shift-delta) * -2);
}
.kumonosu-card:has(+ .kumonosu-card + .kumonosu-card + .kumonosu-card:hover) {
--arc-shift: calc(var(--arc-shift-delta) * -1);
}
/* 画像スタイル */
.kumonosu-image {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
border-radius: inherit;
}
Explanation 詳しい説明
仕様
このレイアウトは、JavaScriptを一切使用せず、HTMLとCSSのみで構成されています。
各カードは円弧状のパス上に配置されており、初期状態ではコンパクトな扇形レイアウトになります。
カードごとにインデックスを持たせることで、一定の間隔を保った配置を実現しています。
ホバー時の挙動
カードにホバーすると、以下の動きが発生します。
- ホバーされたカードは、わずかに手前方向へ移動
- 右側のカードは、距離に応じて右方向へ段階的にシフト
- 左側のカードは、距離に応じて左方向へ段階的にシフト
これにより、ホバーされたカードを中心として、カード全体が扇を開くように広がる動きになります。
動きは段階的に弱まる設計になっているため、全体として自然で破綻のないアニメーションになります。
挙動の制御方法
ホバー時の挙動は、:hover と :has() セレクタを組み合わせて制御しています。
「どのカードがホバーされているか」を基準に、その前後に位置するカードへ異なるシフト量を与えることで、JavaScriptを使わずに連動した動きを実現しています。
位置の変化は offset-path 上で行われるため、カードの回転角度や並び方も自然に保たれます。
カスタマイズ
CSSカスタムプロパティを調整することで、見た目や動きの印象を変更できます。
- カードサイズ
--card-widthを変更すると、カード全体の大きさが変わります。 - 扇の広がり具合
--radiusや--arc-sizeを調整すると、扇形のカーブや開き具合を変更できます。 - ホバー時の移動量
--arc-shift-deltaを変更すると、周囲のカードがどれだけ動くかを調整できます。 - アニメーションの質感
--card-trans-durationや--card-trans-easingを変更すると、動きの速さや弾性を調整できます。
注意点
本実装では offset-path や :has() など、比較的新しいCSS仕様を使用しています。
そのため、対応していないブラウザではホバー時の連動アニメーションが正しく動作しない場合があります。
また、タッチデバイスではホバー状態が存在しないため、実運用では focus や active 状態への対応を検討すると安心です。
まとめ
カード単体ではなく、ホバーをきっかけに全体が連動して動くことで、視線誘導と操作感を自然に表現できます。
本デモは、htmlとcssだけでインタラクションを設計するためのひとつの実践例です。