05가로 타임라인 패럴랙스
세로로 스크롤하는 동안 카드 줄이 가로로 흘러가 타임라인처럼 읽힌다.
쓰임연혁 페이지, 제품 로드맵
translateXscroll(root)가로 흐름

<main class="stage is-demo">
<div class="hline">
<span class="hline__label">타임라인</span>
<div class="hline__track">
<span class="hline__card">01</span>
<span class="hline__card">02</span>
<span class="hline__card">03</span>
<span class="hline__card">04</span>
</div>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개. 변형은 이것만 바꾼다 ──────────────────────────
$travel: 120px; // 가로 이동 거리
$gap: 14px; // 카드 사이 간격
$color: $subject-cream; // 카드 색 (ink 무대라 cream)
@include stage($stage-ink);
.hline {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
display: grid;
align-content: center;
gap: 18px;
}
.hline__label {
padding-left: 16px;
color: rgba($color, .7);
font: 700 clamp(12px, 3.6vw, 14px)/1 -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
letter-spacing: .04em;
}
.hline__track {
display: flex;
gap: $gap;
padding-left: 16px;
// ── 실제 사용 코드: 세로 스크롤을 그대로 가로 이동으로 쓴다 ──
@supports (animation-timeline: scroll()) {
animation: travel linear both;
animation-timeline: scroll(root);
}
@supports not (animation-timeline: scroll()) { transform: none; }
}
.hline__card {
flex: none;
width: 64px;
height: 64px;
border-radius: $r-card;
background: $color;
color: $stage-ink;
display: grid;
place-items: center;
font: 700 18px/1 -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
}
@keyframes travel { from { transform: translateX(0); } to { transform: translateX(-#{$travel}); } }
// ── 데모(.is-demo): 왕복 루프로 흉내 ────────
.stage.is-demo .hline__track { animation: travel-loop $dur-loop ease-in-out infinite; }
@keyframes travel-loop { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(-$travel); } }
@media (prefers-reduced-motion: reduce) {
.hline__track, .stage.is-demo .hline__track { animation: none !important; transform: none; }
}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);
}
.hline {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
display: grid;
align-content: center;
gap: 18px;
}
.hline__label {
padding-left: 16px;
color: rgba(255, 247, 230, 0.7);
font: 700 clamp(12px, 3.6vw, 14px)/1 -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
letter-spacing: 0.04em;
}
.hline__track {
display: flex;
gap: 14px;
padding-left: 16px;
}
@supports (animation-timeline: scroll()) {
.hline__track {
animation: travel linear both;
animation-timeline: scroll(root);
}
}
@supports not (animation-timeline: scroll()) {
.hline__track {
transform: none;
}
}
.hline__card {
flex: none;
width: 64px;
height: 64px;
border-radius: 16px;
background: #fff7e6;
color: #17141a;
display: grid;
place-items: center;
font: 700 18px/1 -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
}
@keyframes travel {
from {
transform: translateX(0);
}
to {
transform: translateX(-120px);
}
}
.stage.is-demo .hline__track {
animation: travel-loop 2s ease-in-out infinite;
}
@keyframes travel-loop {
0%, 100% {
transform: translateX(0);
}
50% {
transform: translateX(-120px);
}
}
@media (prefers-reduced-motion: reduce) {
.hline__track, .stage.is-demo .hline__track {
animation: none !important;
transform: none;
}
}import type { CSSProperties, ReactNode } from "react";
import styles from "./HorizontalTimeline.module.scss";
export type HorizontalTimelineProps = {
/** 카드 내용 (숫자·짧은 라벨) */
items: ReactNode[];
label?: string;
/** 가로 이동 거리 (기본 120px) */
travel?: number;
/** 카드 사이 간격 (기본 14px) */
gap?: number;
className?: string;
};
type Vars = CSSProperties & { "--travel"?: string; "--gap"?: string };
export default function HorizontalTimeline({
items,
label = "Timeline",
travel = 120,
gap = 14,
className = "",
}: HorizontalTimelineProps) {
const vars: Vars = { "--travel": `${travel}px`, "--gap": `${gap}px` };
return (
<div className={`${styles.hline} ${className}`} style={vars}>
<span className={styles.label}>{label}</span>
<div className={styles.track}>
{items.map((item, i) => (
<span key={i} className={styles.card}>
{item}
</span>
))}
</div>
</div>
);
}05 가로 타임라인 패럴랙스 — 측정 누적면적 13.686% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · CSS 패럴랙스 스크롤 9종 — AOS·GSAP 없이, JS 0줄
이 데모가 속한 글
CSS 패럴랙스 스크롤 9종 — AOS·GSAP 없이, JS 0줄CSS 패럴랙스 스크롤 아홉 개를 모았습니다. 레이어마다 이동 속도를 다르게 주는 원리 하나로 배경이 내용보다 느리게 움직이는 착시를 만듭니다. 격자에서는 아홉 개가 2초 간격으로 자동 반복되니 먼저 눈으로 훑고 마 이 글의 데모 전부 보기


