05フリップギャラリー
格子タイル4枚が0.15秒ずつずれて順番にめくれ、カテゴリを見せる。006 はカード1枚だが、これは複数枚が波のように続く。
使いどころメディアギャラリーのカテゴリ格子、ダッシュボードウィジェット紹介
backface-visibilityanimation-delay 시차grid

<main class="stage">
<ul class="gallery is-demo">
<li class="tile"><div class="tile__inner">
<div class="tile__face tile__face--front" aria-hidden="true"></div>
<div class="tile__face tile__face--back">写真</div>
</div></li>
<li class="tile"><div class="tile__inner">
<div class="tile__face tile__face--front" aria-hidden="true"></div>
<div class="tile__face tile__face--back">動画</div>
</div></li>
<li class="tile"><div class="tile__inner">
<div class="tile__face tile__face--front" aria-hidden="true"></div>
<div class="tile__face tile__face--back">ドキュメント</div>
</div></li>
<li class="tile"><div class="tile__inner">
<div class="tile__face tile__face--front" aria-hidden="true"></div>
<div class="tile__face tile__face--back">音楽</div>
</div></li>
</ul>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────
$duration: $dur-base; // 타일 하나가 뒤집히는 시간
$easing: $ease-out;
$color: $subject-blue-deep; // 뒷면(카테고리) 배경 (크림 글자가 얹히므로 진한 파랑)
@include stage($stage-ink);
.gallery {
display: grid;
grid-template-columns: repeat(2, 74px);
gap: $sp-2;
list-style: none;
margin: 0;
padding: 0;
perspective: 700px; // 격자 전체가 같은 시점을 공유
}
.tile { width: 74px; height: 74px; }
.tile__inner {
position: relative;
width: 100%;
height: 100%;
transform-style: preserve-3d;
}
.tile__face {
position: absolute;
inset: 0;
display: grid;
place-items: center;
border-radius: $r-sm;
backface-visibility: hidden; // 뒤로 넘어간 면 글자가 반전돼 비치지 않게
}
.tile__face--front { background: $subject-cream; }
.tile__face--back { background: $color; color: $subject-cream; font-weight: 700; font-size: 12px; line-height: 1; font-family: inherit; transform: rotateY(180deg); }
// 4칸이 0.15초씩 시차를 두고 같은 동작을 한다 — 물결처럼 뒤집힌다
.gallery.is-demo .tile:nth-child(1) .tile__inner { animation: flip $dur-loop $easing infinite; }
.gallery.is-demo .tile:nth-child(2) .tile__inner { animation: flip $dur-loop $easing infinite; animation-delay: .15s; }
.gallery.is-demo .tile:nth-child(3) .tile__inner { animation: flip $dur-loop $easing infinite; animation-delay: .3s; }
.gallery.is-demo .tile:nth-child(4) .tile__inner { animation: flip $dur-loop $easing infinite; animation-delay: .45s; }
@keyframes flip {
0%, 15% { transform: rotateY(0deg); }
50% { transform: rotateY(180deg); }
85%, 100% { transform: rotateY(360deg); } // 360 = 0 이라 다음 루프로 튀지 않고 이어진다
}
// ── 접근성: 움직임 줄이기 설정이면 뒷면(정보)에서 고정 ────────────
@media (prefers-reduced-motion: reduce) {
.gallery.is-demo .tile__inner { animation: none; transform: rotateY(180deg); }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #17141a;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.gallery {
display: grid;
grid-template-columns: repeat(2, 74px);
gap: 8px;
list-style: none;
margin: 0;
padding: 0;
perspective: 700px;
}
.tile {
width: 74px;
height: 74px;
}
.tile__inner {
position: relative;
width: 100%;
height: 100%;
transform-style: preserve-3d;
}
.tile__face {
position: absolute;
inset: 0;
display: grid;
place-items: center;
border-radius: 8px;
backface-visibility: hidden;
}
.tile__face--front {
background: #fff7e6;
}
.tile__face--back {
background: #0a4fe8;
color: #fff7e6;
font-weight: 700;
font-size: 12px;
line-height: 1;
font-family: inherit;
transform: rotateY(180deg);
}
.gallery.is-demo .tile:nth-child(1) .tile__inner {
animation: flip 2s ease-out infinite;
}
.gallery.is-demo .tile:nth-child(2) .tile__inner {
animation: flip 2s ease-out infinite;
animation-delay: 0.15s;
}
.gallery.is-demo .tile:nth-child(3) .tile__inner {
animation: flip 2s ease-out infinite;
animation-delay: 0.3s;
}
.gallery.is-demo .tile:nth-child(4) .tile__inner {
animation: flip 2s ease-out infinite;
animation-delay: 0.45s;
}
@keyframes flip {
0%, 15% {
transform: rotateY(0deg);
}
50% {
transform: rotateY(180deg);
}
85%, 100% {
transform: rotateY(360deg);
}
}
@media (prefers-reduced-motion: reduce) {
.gallery.is-demo .tile__inner {
animation: none;
transform: rotateY(180deg);
}
}import type { CSSProperties } from "react";
import styles from "./FlipGallery.module.scss";
export type FlipGalleryProps = {
duration?: number;
easing?: string;
/** 뒷면(카테고리) 배경 */
color?: string;
tiles?: string[];
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string; "--delay"?: string };
export default function FlipGallery({
duration = 2000,
easing = "ease-out",
color = "#2f6df6",
tiles = ["사진", "동영상", "문서", "음악"],
}: FlipGalleryProps) {
const base: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<ul className={styles.gallery}>
{tiles.map((t, i) => (
<li key={t} className={styles.tile}>
<div className={styles.inner} style={{ ...base, "--delay": `${i * 0.15}s` } as Vars}>
<div className={`${styles.face} ${styles.front}`} aria-hidden="true" />
<div className={`${styles.face} ${styles.back}`}>{t}</div>
</div>
</li>
))}
</ul>
);
}05 フリップギャラリー — 計測 累積面積 15.097% · 動いたフレーム 20/23GODRICH オリジナル · SCSS と React は zip に同梱 · 【一行でコピペOK】CSS 3D アニメーション9選
このデモが属する記事
【一行でコピペOK】CSS 3D アニメーション9選css 3d アニメーションは、perspectiveとtransform-style: preserve-3dだけで奥行きを作るCSSの技法です。回転するキューブや円周上のカルーセル、ホバーで傾くカードなど9種類を選び、それぞれ一行でコピ この記事のデモをすべて見る


