02ポラロイドスタックギャラリー
写真4枚がポラロイドのようにわずかに角度をずらして重なり、一番上の1枚が横にずれて回転しながら退くと次の写真が現れる、という流れを順番に繰り返します。
使いどころ何枚かを1枚ずつめくって見せたい旅行アルバム、口コミ写真を情緒的にまとめたレビューセクション
rotate() 각도 오프셋z-index 스택transform 순환 키프레임

<main class="stage">
<div class="stack is-demo" aria-label="めくって見るポラロイド写真スタック" tabindex="0">
<div class="polaroid polaroid--1"><span class="polaroid__photo polaroid__photo--a"></span></div>
<div class="polaroid polaroid--2"><span class="polaroid__photo polaroid__photo--b"></span></div>
<div class="polaroid polaroid--3"><span class="polaroid__photo polaroid__photo--c"></span></div>
<div class="polaroid polaroid--4"><span class="polaroid__photo polaroid__photo--d"></span></div>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 한 장이 물러났다 돌아오는 주기
$easing: $ease-spring;
$color: $subject-lilac; // 폴라로이드 사진 색 포인트
@include stage($stage-paper);
.stack {
position: relative;
width: 128px;
height: 148px;
outline: none;
}
.polaroid {
position: absolute;
inset: 0;
padding: $sp-2 $sp-2 $sp-6;
background: $subject-cream;
border-radius: $r-xs;
box-shadow: $shadow-press;
transform-origin: 50% 85%; // 규칙8: 폴라로이드 회전 피벗 아래축
}
.polaroid__photo {
display: block;
width: 100%;
height: 100%;
border-radius: $r-xs;
}
.polaroid__photo--a { background: linear-gradient(160deg, $subject-pink 0%, $color 100%); }
.polaroid__photo--b { background: linear-gradient(160deg, $subject-mint 0%, $color 100%); }
.polaroid__photo--c { background: linear-gradient(160deg, $subject-sky 0%, $color 100%); }
.polaroid__photo--d { background: linear-gradient(160deg, $color 0%, $subject-pink 100%); }
.polaroid--1 { transform: rotate(-8deg) translate(-6px, 4px); z-index: 1; }
.polaroid--2 { transform: rotate(5deg) translate(4px, 1px); z-index: 2; }
.polaroid--3 { transform: rotate(-3deg) translate(-2px, -2px); z-index: 3; }
.polaroid--4 { transform: rotate(2deg) translate(1px, -4px); z-index: 4; }
// 네 장이 차례로 살짝씩 밀려났다 제자리로 돌아온다 — 항상 한 장은 움직이는 중이라 스택이 계속 넘겨지는 것처럼 보인다
.stack.is-demo .polaroid--1 { animation: shuffle-1 $duration $easing infinite; }
.stack.is-demo .polaroid--2 { animation: shuffle-2 $duration $easing infinite; animation-delay: -0.5s; }
.stack.is-demo .polaroid--3 { animation: shuffle-3 $duration $easing infinite; animation-delay: -1s; }
.stack.is-demo .polaroid--4 { animation: shuffle-4 $duration $easing infinite; animation-delay: -1.5s; }
@keyframes shuffle-1 {
0%, 10%, 55%, 100% { transform: rotate(-8deg) translate(-6px, 4px); opacity: 1; }
30% { transform: rotate(-24deg) translate(-64px, -22px) scale(.94); opacity: .25; }
}
@keyframes shuffle-2 {
0%, 10%, 55%, 100% { transform: rotate(5deg) translate(4px, 1px); opacity: 1; }
30% { transform: rotate(20deg) translate(60px, -20px) scale(.94); opacity: .25; }
}
@keyframes shuffle-3 {
0%, 10%, 55%, 100% { transform: rotate(-3deg) translate(-2px, -2px); opacity: 1; }
30% { transform: rotate(-18deg) translate(-58px, -24px) scale(.94); opacity: .25; }
}
@keyframes shuffle-4 {
0%, 10%, 55%, 100% { transform: rotate(2deg) translate(1px, -4px); opacity: 1; }
30% { transform: rotate(16deg) translate(56px, -26px) scale(.94); opacity: .25; }
}
@media (prefers-reduced-motion: reduce) {
.stack.is-demo .polaroid--1,
.stack.is-demo .polaroid--2,
.stack.is-demo .polaroid--3,
.stack.is-demo .polaroid--4 { animation: none; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ffffff;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.stack {
position: relative;
width: 128px;
height: 148px;
outline: none;
}
.polaroid {
position: absolute;
inset: 0;
padding: 8px 8px 24px;
background: #fff7e6;
border-radius: 4px;
box-shadow: 0 6px 12px rgba(23, 20, 26, 0.32);
transform-origin: 50% 85%;
}
.polaroid__photo {
display: block;
width: 100%;
height: 100%;
border-radius: 4px;
}
.polaroid__photo--a {
background: linear-gradient(160deg, #ff9ebb 0%, #b9a0ff 100%);
}
.polaroid__photo--b {
background: linear-gradient(160deg, #4cd4a6 0%, #b9a0ff 100%);
}
.polaroid__photo--c {
background: linear-gradient(160deg, #6ec8ff 0%, #b9a0ff 100%);
}
.polaroid__photo--d {
background: linear-gradient(160deg, #b9a0ff 0%, #ff9ebb 100%);
}
.polaroid--1 {
transform: rotate(-8deg) translate(-6px, 4px);
z-index: 1;
}
.polaroid--2 {
transform: rotate(5deg) translate(4px, 1px);
z-index: 2;
}
.polaroid--3 {
transform: rotate(-3deg) translate(-2px, -2px);
z-index: 3;
}
.polaroid--4 {
transform: rotate(2deg) translate(1px, -4px);
z-index: 4;
}
.stack.is-demo .polaroid--1 {
animation: shuffle-1 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.stack.is-demo .polaroid--2 {
animation: shuffle-2 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
animation-delay: -0.5s;
}
.stack.is-demo .polaroid--3 {
animation: shuffle-3 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
animation-delay: -1s;
}
.stack.is-demo .polaroid--4 {
animation: shuffle-4 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
animation-delay: -1.5s;
}
@keyframes shuffle-1 {
0%, 10%, 55%, 100% {
transform: rotate(-8deg) translate(-6px, 4px);
opacity: 1;
}
30% {
transform: rotate(-24deg) translate(-64px, -22px) scale(0.94);
opacity: 0.25;
}
}
@keyframes shuffle-2 {
0%, 10%, 55%, 100% {
transform: rotate(5deg) translate(4px, 1px);
opacity: 1;
}
30% {
transform: rotate(20deg) translate(60px, -20px) scale(0.94);
opacity: 0.25;
}
}
@keyframes shuffle-3 {
0%, 10%, 55%, 100% {
transform: rotate(-3deg) translate(-2px, -2px);
opacity: 1;
}
30% {
transform: rotate(-18deg) translate(-58px, -24px) scale(0.94);
opacity: 0.25;
}
}
@keyframes shuffle-4 {
0%, 10%, 55%, 100% {
transform: rotate(2deg) translate(1px, -4px);
opacity: 1;
}
30% {
transform: rotate(16deg) translate(56px, -26px) scale(0.94);
opacity: 0.25;
}
}
@media (prefers-reduced-motion: reduce) {
.stack.is-demo .polaroid--1,
.stack.is-demo .polaroid--2,
.stack.is-demo .polaroid--3,
.stack.is-demo .polaroid--4 {
animation: none;
}
}import type { CSSProperties } from "react";
import styles from "./PolaroidStack.module.scss";
export type PolaroidStackProps = {
/** 한 장이 물러났다 돌아오는 주기 (기본 2000ms) */
duration?: number;
easing?: string;
/** 사진 색 포인트 */
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
/** 네 장이 차례로 살짝씩 밀려났다 제자리로 돌아와, 스택이 계속 넘겨지는 것처럼 보인다 */
export default function PolaroidStack({
duration = 2000,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#b9a0ff",
}: PolaroidStackProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<div className={styles.stack} style={vars} tabIndex={0} aria-label="A stack of polaroid photos you flip through">
<div className={`${styles.polaroid} ${styles.p1}`}><span className={`${styles.photo} ${styles.photoA}`} /></div>
<div className={`${styles.polaroid} ${styles.p2}`}><span className={`${styles.photo} ${styles.photoB}`} /></div>
<div className={`${styles.polaroid} ${styles.p3}`}><span className={`${styles.photo} ${styles.photoC}`} /></div>
<div className={`${styles.polaroid} ${styles.p4}`}><span className={`${styles.photo} ${styles.photoD}`} /></div>
</div>
);
}02 ポラロイドスタックギャラリー — 計測 累積面積 29.485% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · ポートフォリオ ギャラリー UI 9選 — ドラッグからモーフ切替まで
このデモが属する記事
ポートフォリオ ギャラリー UI 9選 — ドラッグからモーフ切替までポートフォリオ ギャラリーとは、写真や作品を格子状に並べ、ホバーやクリックで拡大・絞り込み・並び替え・切り替えが起こる画面のことです。Pinterestで保存したあのポートフォリオギャラリーを、9通りの方式に分けてそのままコードでお届けしま この記事のデモをすべて見る


