07カードスタックスワイプ
複数のカードを同じ位置に重ねて積んでおき、一番上のカードが横にスライドして抜けると次のカードがその場所を埋める方式で、スクロールボックス自体がありません。
使いどころマッチングアプリのようなカードめくり、おすすめカードを1枚ずつ見る
grid-areatransformz-index

<main class="stage">
<div class="cs">
<div class="cs__deck">
<div class="cs__card cs__card--1"></div>
<div class="cs__card cs__card--2"></div>
<div class="cs__card cs__card--3"></div>
</div>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────
$duration: $dur-base;
$easing: $ease-spring;
$color: $subject-blue;
@include stage($stage-orange);
.cs { width: min(170px, 38vw); }
// ── 실제 동작: 카드 3장이 같은 자리(grid-area: 1/1)에 겹쳐 있다. 스크롤 상자가 없다 ──
.cs__deck {
display: grid;
aspect-ratio: 3 / 4;
}
.cs__card {
grid-area: 1 / 1;
border-radius: $r-card;
box-shadow: $shadow-raised;
animation: cs-cycle $dur-loop $easing infinite;
}
.cs__card--1 { background: linear-gradient(160deg, $color, #d6ddeb); animation-delay: -0s; }
.cs__card--2 { background: linear-gradient(160deg, $stage-yellow, #ebe7d6); animation-delay: -.667s; }
.cs__card--3 { background: linear-gradient(160deg, $subject-ink, #4a3f5c); animation-delay: -1.333s; }
// 맨 위 카드가 옆으로 미끄러지며 빠지고, 빠진 카드는 스택 맨 뒤에서 다시 앞으로 올라온다
@keyframes cs-cycle {
0%, 4% { transform: translate(0, 0) scale(1) rotate(0deg); opacity: 1; z-index: 3; }
29% { transform: translate(42%, 6%) scale(1) rotate(12deg); opacity: 0; z-index: 3; }
29.1% { transform: translate(-6%, 6%) scale(.9) rotate(-4deg); opacity: 0; z-index: 1; }
33%, 62% { transform: translate(-6%, 6%) scale(.9) rotate(-4deg); opacity: .85; z-index: 1; }
66%, 96% { transform: translate(6%, 4%) scale(.95) rotate(3deg); opacity: .92; z-index: 2; }
100% { transform: translate(0, 0) scale(1) rotate(0deg); opacity: 1; z-index: 3; }
}
// 자동 순환이 전부다 — 마우스를 올리면 잠깐 멈춰서 지금 카드를 편하게 본다
.cs:hover .cs__card { animation-play-state: paused; }
// ── 접근성 ──────────────────────────────────────────
@media (prefers-reduced-motion: reduce) {
.cs__card { animation: none !important; }
.cs__card--1 { opacity: 1; z-index: 3; transform: none; }
.cs__card--2, .cs__card--3 { opacity: 0; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ff4d1f;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.cs {
width: min(170px, 38vw);
}
.cs__deck {
display: grid;
aspect-ratio: 3/4;
}
.cs__card {
grid-area: 1/1;
border-radius: 16px;
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
animation: cs-cycle 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.cs__card--1 {
background: linear-gradient(160deg, #2f6df6, #d6ddeb);
animation-delay: -0s;
}
.cs__card--2 {
background: linear-gradient(160deg, #ffd23f, #ebe7d6);
animation-delay: -0.667s;
}
.cs__card--3 {
background: linear-gradient(160deg, #17141a, #4a3f5c);
animation-delay: -1.333s;
}
@keyframes cs-cycle {
0%, 4% {
transform: translate(0, 0) scale(1) rotate(0deg);
opacity: 1;
z-index: 3;
}
29% {
transform: translate(42%, 6%) scale(1) rotate(12deg);
opacity: 0;
z-index: 3;
}
29.1% {
transform: translate(-6%, 6%) scale(0.9) rotate(-4deg);
opacity: 0;
z-index: 1;
}
33%, 62% {
transform: translate(-6%, 6%) scale(0.9) rotate(-4deg);
opacity: 0.85;
z-index: 1;
}
66%, 96% {
transform: translate(6%, 4%) scale(0.95) rotate(3deg);
opacity: 0.92;
z-index: 2;
}
100% {
transform: translate(0, 0) scale(1) rotate(0deg);
opacity: 1;
z-index: 3;
}
}
.cs:hover .cs__card {
animation-play-state: paused;
}
@media (prefers-reduced-motion: reduce) {
.cs__card {
animation: none !important;
}
.cs__card--1 {
opacity: 1;
z-index: 3;
transform: none;
}
.cs__card--2, .cs__card--3 {
opacity: 0;
}
}import type { CSSProperties } from "react";
import styles from "./StackedCardSwipe.module.scss";
export type StackedCardSwipeProps = {
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function StackedCardSwipe({ duration = 2000, easing = "cubic-bezier(.2,.8,.2,1)", color = "#2f6df6" }: StackedCardSwipeProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<div className={styles.cs} style={vars}>
{/* 카드 3장이 같은 자리(grid-area: 1/1)에 겹쳐 있고, @keyframes 하나가 순서대로 옆으로 빠진다 — 0줄 JS */}
<div className={styles.deck}>
{[1, 2, 3].map((n) => (
<div key={n} className={`${styles.card} ${styles["card" + n]}`} />
))}
</div>
</div>
);
}07 カードスタックスワイプ — 計測 累積面積 41.574% · 動いたフレーム 17/23GODRICH オリジナル · SCSS と React は zip に同梱 · css カルーセル スライダーの作り方9選 — コピペでOK
このデモが属する記事
css カルーセル スライダーの作り方9選 — コピペでOKcss カルーセル スライダーとは、写真やカードを何枚か並べて画面の中で順番に切り替えて見せる部品のことです。指でめくったりボタンを押したりすると次の一枚が現れ、JavaScriptを書かなくてもCSSだけで動かせます。スクロールからラジオ この記事のデモをすべて見る


