cssだけで作る、ホバーに連動して扇状に広がるカードレイアウト

ホバー

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仕様を使用しています。

そのため、対応していないブラウザではホバー時の連動アニメーションが正しく動作しない場合があります。

また、タッチデバイスではホバー状態が存在しないため、実運用では focusactive 状態への対応を検討すると安心です。

まとめ

カード単体ではなく、ホバーをきっかけに全体が連動して動くことで、視線誘導と操作感を自然に表現できます。

本デモは、htmlとcssだけでインタラクションを設計するためのひとつの実践例です。