CSSだけで作る、重なった画像をめくるスライダー

アニメーション

CSSだけで作る、重なった画像をめくるスライダー

投稿日2026/02/12

更新日2026/2/6

JavaScriptなしで、操作できるスライダーを作りたい。
このサンプルは、ラジオボタンと label を使って、CSSだけで画像を切り替えるカード型スライダーです。

画像は重なった状態から前面へ移動し、めくれるような動きで切り替わります。
シンプルな構造ながら、視覚的に印象に残る演出を加えています。

Preview プレビュー

Code コード

<div class="kumonosu-slider-layout">
    <div class="kumonosu-cards">

        <!-- カード 1 -->
        <input type="radio" id="kumonosu-radio-1" name="radio-card" checked>
        <article class="kumonosu-card" style="--angle:4deg">
            <img class="kumonosu-card-img" src="https://picsum.photos/id/10/400/400" alt="風景1">
            <div class="kumonosu-card-data">
                <span class="kumonosu-card-num">1 / 7</span>
                <h2>静寂の湖畔</h2>
                <p>朝霧に包まれた静かな湖。澄んだ空気と穏やかな水面が、訪れる人の心を落ち着かせてくれます。</p>
                <div class="kumonosu-card-footer">
                    <label for="kumonosu-radio-7" aria-label="前へ">❮</label>
                    <label for="kumonosu-radio-2" aria-label="次へ">❯</label>
                </div>
            </div>
        </article>

        <!-- カード 2 -->
        <input type="radio" id="kumonosu-radio-2" name="radio-card">
        <article class="kumonosu-card" style="--angle:-8deg">
            <img class="kumonosu-card-img" src="https://picsum.photos/id/28/400/400" alt="風景2">
            <div class="kumonosu-card-data">
                <span class="kumonosu-card-num">2 / 7</span>
                <h2>深い森の小径</h2>
                <p>木漏れ日が差し込む原生林。鳥のさえずりと葉の擦れ合う音が、自然の奏でる音楽のように響きます。</p>
                <div class="kumonosu-card-footer">
                    <label for="kumonosu-radio-1" aria-label="前へ">❮</label>
                    <label for="kumonosu-radio-3" aria-label="次へ">❯</label>
                </div>
            </div>
        </article>

        <!-- カード 3 -->
        <input type="radio" id="kumonosu-radio-3" name="radio-card">
        <article class="kumonosu-card" style="--angle:-7deg">
            <img class="kumonosu-card-img" src="https://picsum.photos/id/122/400/400" alt="風景3">
            <div class="kumonosu-card-data">
                <span class="kumonosu-card-num">3 / 7</span>
                <h2>近代都市の灯り</h2>
                <p>夜空にそびえ立つ摩天楼。都会のエネルギーが凝縮された夜景は、常に新しい物語を予感させます。</p>
                <div class="kumonosu-card-footer">
                    <label for="kumonosu-radio-2" aria-label="前へ">❮</label>
                    <label for="kumonosu-radio-4" aria-label="次へ">❯</label>
                </div>
            </div>
        </article>

        <!-- カード 4 -->
        <input type="radio" id="kumonosu-radio-4" name="radio-card">
        <article class="kumonosu-card" style="--angle:11deg">
            <img class="kumonosu-card-img" src="https://picsum.photos/id/124/400/400" alt="風景4">
            <div class="kumonosu-card-data">
                <span class="kumonosu-card-num">4 / 7</span>
                <h2>波打ち際の休息</h2>
                <p>どこまでも続く青い水平線。寄せては返す波の音が、日常の忙しさを忘れさせてくれるでしょう。</p>
                <div class="kumonosu-card-footer">
                    <label for="kumonosu-radio-3" aria-label="前へ">❮</label>
                    <label for="kumonosu-radio-5" aria-label="次へ">❯</label>
                </div>
            </div>
        </article>

        <!-- カード 5 -->
        <input type="radio" id="kumonosu-radio-5" name="radio-card" >
        <article class="kumonosu-card" style="--angle:13deg">
            <img class="kumonosu-card-img" src="https://picsum.photos/id/29/400/400" alt="風景5">
            <div class="kumonosu-card-data">
                <span class="kumonosu-card-num">5 / 7</span>
                <h2>峻険な山脈</h2>
                <p>雲を突き抜けるような高い山々。その雄大な姿は、自然の力強さと厳かさを教えてくれます。</p>
                <div class="kumonosu-card-footer">
                    <label for="kumonosu-radio-4" aria-label="前へ">❮</label>
                    <label for="kumonosu-radio-6" aria-label="次へ">❯</label>
                </div>
            </div>
        </article>

        <!-- カード 6 -->
        <input type="radio" id="kumonosu-radio-6" name="radio-card">
        <article class="kumonosu-card" style="--angle:-17deg">
            <img class="kumonosu-card-img" src="https://picsum.photos/id/234/400/400" alt="風景6">
            <div class="kumonosu-card-data">
                <span class="kumonosu-card-num">6 / 7</span>
                <h2>冬の静寂</h2>
                <p>一面が真っ白に染まる雪景色。冷たい空気が感覚を研ぎ澄ませ、世界を美しく際立たせます。</p>
                <div class="kumonosu-card-footer">
                    <label for="kumonosu-radio-5" aria-label="前へ">❮</label>
                    <label for="kumonosu-radio-7" aria-label="次へ">❯</label>
                </div>
            </div>
        </article>

        <!-- カード 7 -->
        <input type="radio" id="kumonosu-radio-7" name="radio-card" >
        <article class="kumonosu-card" style="--angle:20deg">
            <img class="kumonosu-card-img" src="https://picsum.photos/id/152/400/400" alt="風景7">
            <div class="kumonosu-card-data">
                <span class="kumonosu-card-num">7 / 7</span>
                <h2>黄昏時の風景</h2>
                <p>空が茜色に染まるマジックアワー。今日という一日の終わりを告げる、最も美しい瞬間の一つです。</p>
                <div class="kumonosu-card-footer">
                    <label for="kumonosu-radio-6" aria-label="前へ">❮</label>
                    <label for="kumonosu-radio-1" aria-label="次へ">❯</label>
                </div>
            </div>
        </article>
    </div>
</div>
/* 1. 基本スタイル */
body {
	font: 1rem "Helvetica Neue", Arial, "Hiragino Kaku Gothic ProN", "Hiragino Sans", Meiryo, sans-serif;
	color-scheme: dark light;
	background-color: #f0f0f0;
	margin: 0;
	line-height: 1.6;
}
/* 2. レイアウト用ラッパー */
.kumonosu-slider-layout {
	min-height: 100svh;
	display: grid;
	place-content: center;
	padding: 1rem;
	overflow: hidden;
}
/* --- スタイル設定 --- */
*, ::before, ::after {
	margin: 0;
	box-sizing: border-box;
}
@property --angle {
	syntax: "<angle>";
	initial-value: 0deg;
	inherits: true;
}
img {
	max-width: 100%;
	display: block;
}
input[type="radio"] {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border-width: 0;
}
.kumonosu-cards {
	--img-w: 220px;
	--duration: 300ms;
	--img-easing: cubic-bezier(0.34, 1.56, 0.64, 1);
	width: min(100% - 4rem, 800px);
	margin-inline: auto;
	display: grid;
}
.kumonosu-card {
	--cards-grid-cols: auto;
	--cards-grid-rows: var(--img-w) auto;
	--cards-grid-gap: 2rem;
	--cards-footer-justify: center;
	grid-area: 1/1;
	display: grid;
	place-items: center;
	grid-template-columns: var(--cards-grid-cols);
	grid-template-rows: var(--cards-grid-rows);
	gap: var(--cards-grid-gap);
}
@media (min-width: 601px) {
	.kumonosu-card {
		--cards-grid-cols: var(--img-w) auto;
		--cards-grid-rows: auto;
		--cards-grid-gap: 4rem;
		--cards-footer-justify: start;
	}
}
.kumonosu-card-img {
	width: 220px;
	height: 220px;
	aspect-ratio: 1 / 1;
	rotate: var(--angle, 0deg);
	border-radius: 12px;
	border: 4px solid #FFF;
	overflow: hidden;
	transform-origin: center;
	object-fit: cover;
	box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
	background-color: #ddd;
}
/* アニメーションロジック */
input:nth-of-type(1):checked+.kumonosu-card~.kumonosu-card>.kumonosu-card-img {
	animation: straighten-img-1 calc(var(--duration) * 2) forwards;
	animation-timing-function: var(--img-easing);
}
.kumonosu-card:has(~input:nth-of-type(2):checked)>.kumonosu-card-img, input:nth-of-type(2):checked+.kumonosu-card~.kumonosu-card>.kumonosu-card-img {
	animation: straighten-img-2 calc(var(--duration) * 2) forwards;
	animation-timing-function: var(--img-easing);
}
.kumonosu-card:has(~input:nth-of-type(3):checked)>.kumonosu-card-img, input:nth-of-type(3):checked+.kumonosu-card~.kumonosu-card>.kumonosu-card-img {
	animation: straighten-img-3 calc(var(--duration) * 2) forwards;
	animation-timing-function: var(--img-easing);
}
.kumonosu-card:has(~input:nth-of-type(4):checked)>.kumonosu-card-img, input:nth-of-type(4):checked+.kumonosu-card~.kumonosu-card>.kumonosu-card-img {
	animation: straighten-img-4 calc(var(--duration) * 2) forwards;
	animation-timing-function: var(--img-easing);
}
.kumonosu-card:has(~input:nth-of-type(5):checked)>.kumonosu-card-img, input:nth-of-type(5):checked+.kumonosu-card~.kumonosu-card>.kumonosu-card-img {
	animation: straighten-img-5 calc(var(--duration) * 2) forwards;
	animation-timing-function: var(--img-easing);
}
.kumonosu-card:has(~input:nth-of-type(6):checked)>.kumonosu-card-img, input:nth-of-type(6):checked+.kumonosu-card~.kumonosu-card>.kumonosu-card-img {
	animation: straighten-img-6 calc(var(--duration) * 2) forwards;
	animation-timing-function: var(--img-easing);
}
.kumonosu-card:has(~input:nth-of-type(7):checked)>.kumonosu-card-img, input:nth-of-type(7):checked+.kumonosu-card~.kumonosu-card>.kumonosu-card-img {
	animation: straighten-img-7 calc(var(--duration) * 2) forwards;
	animation-timing-function: var(--img-easing);
}
@keyframes straighten-img-1 {
	50% {
		--angle: 0deg;
	}
}
@keyframes straighten-img-2 {
	50% {
		--angle: 0deg;
	}
}
@keyframes straighten-img-3 {
	50% {
		--angle: 0deg;
	}
}
@keyframes straighten-img-4 {
	50% {
		--angle: 0deg;
	}
}
@keyframes straighten-img-5 {
	50% {
		--angle: 0deg;
	}
}
@keyframes straighten-img-6 {
	50% {
		--angle: 0deg;
	}
}
@keyframes straighten-img-7 {
	50% {
		--angle: 0deg;
	}
}
/* 重なり順 */
.kumonosu-card {
	z-index: -1;
}
input:checked+.kumonosu-card {
	z-index: 10 !important;
}
.kumonosu-card:has(+input:checked) {
	z-index: 9;
}
.kumonosu-card:has(+input + .kumonosu-card + input:checked) {
	z-index: 8;
}
.kumonosu-card:has(+input + .kumonosu-card + input + .kumonosu-card + input:checked) {
	z-index: 7;
}
.kumonosu-card:has(+input + .kumonosu-card + input + .kumonosu-card + input + .kumonosu-card + input:checked) {
	z-index: 6;
}
.kumonosu-card:has(+input + .kumonosu-card + input + .kumonosu-card + input + .kumonosu-card + input + .kumonosu-card + input:checked) {
	z-index: 5;
}
.kumonosu-card:has(+input + .kumonosu-card + input + .kumonosu-card + input + .kumonosu-card + input + .kumonosu-card + input + .kumonosu-card + input:checked) {
	z-index: 4;
}
.kumonosu-card-data {
	display: grid;
	gap: 1rem;
}
.kumonosu-card-data>.kumonosu-card-num {
	opacity: var(--data-opacity, 0);
	font-size: .8rem;
	color: #666;
}
.kumonosu-card-data>h2, .kumonosu-card-data>p {
	transition: var(--duration) ease-in-out;
	transition-delay: var(--data-delay, 0ms);
	opacity: var(--data-opacity, 0);
	translate: 0 var(--data-y, 20px);
}
.kumonosu-card-footer {
	display: flex;
	justify-content: var(--cards-footer-justify);
	gap: 2rem;
}
.kumonosu-card-footer label {
	margin-block-start: auto;
	cursor: pointer;
	pointer-events: var(--card-events, none);
	opacity: var(--data-opacity, 0);
	color: #000;
	background-color: #EEE;
	border-radius: 50%;
	width: 32px;
	height: 32px;
	display: grid;
	place-content: center;
	transition: all 300ms ease-in-out;
}
/* 矢印ホバー時のスタイル:背景を黒に */
.kumonosu-card-footer label:hover {
	color: #FFF;
	background-color: #000;
}
input:checked+.kumonosu-card {
	--data-opacity: 1;
	--data-y: 0;
	--data-delay: var(--duration);
	--card-events: auto;
	transition: z-index;
	transition-delay: 300ms;
}
input:checked+.kumonosu-card>.kumonosu-card-img {
	animation: reveal-img calc(var(--duration) * 2) forwards;
}
@keyframes reveal-img {
	50% {
		translate: -150% 0;
		--angle: 0deg;
	}
}

Explanation 詳しい説明

仕様

このスライダーは、CSSのみで状態管理と表示切り替えを行います。
ラジオボタンの checked 状態を利用し、選択中のカードだけを前面に表示します。

カードは同じ位置に重ねて配置され、z-index によって表示順を制御しています。

  • ラジオボタンで表示状態を管理
  • label for によるクリック操作
  • gridz-index による重なり構造

画像は角度を持った状態から表示され、切り替え時に角度が整いながら横方向へ移動します。
テキストは少し遅れて表示され、視線が自然に流れるようになっています。

カスタム

見た目や動きはCSS変数を調整するだけで変更できます。
構造を変えずにデザインを調整できる点が特徴です。

  • --img-w:画像サイズ
  • --duration:アニメーション速度
  • イージング、余白、矢印ボタンのデザイン調整

カードを追加する場合は、CSS側の定義もあわせて追加します。

注意点

この実装では、:has()@property などの比較的新しいCSS機能を使用しています。
非対応ブラウザでは、重なりやアニメーションが簡略化される場合があります。

また、画像は object-fit: cover を使用しているため、被写体が切れることがあります。

  • :has() 非対応環境では重なり表現が弱くなる
  • @property 非対応環境では回転アニメが滑らかでなくなる
  • 文字や人物が入った画像のトリミングに注意