05Card trượt so le hai bên
So với thanh dọc ở giữa, card lẻ xuất phát từ translateX(-24px), card chẵn từ translateX(24px), cùng trượt về 0 trong khi opacity đi lên từ 0 đến 1.
Dùng choGiới thiệu cột mốc dự án, trang lịch sử công ty
translateXopacity500ms

<main class="stage">
<div class="ts05-timeline is-demo">
<div class="ts05-row ts05-row--l"><div class="ts05-card">2024.01</div></div>
<div class="ts05-row ts05-row--r"><div class="ts05-card">2024.06</div></div>
<div class="ts05-row ts05-row--l"><div class="ts05-card">2024.12</div></div>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────
$duration: $dur-loop;
$easing: $ease-spring;
$color: $subject-blue-deep; // 크림 글자가 얹히므로 진한 파랑
@include stage($stage-ink);
.ts05-timeline {
position: relative;
width: min(280px, 84vw);
display: flex;
flex-direction: column;
gap: $sp-4;
// 가운데 세로선
&::before {
content: "";
position: absolute;
left: 50%;
top: 0;
width: 2px;
height: 100%;
background: rgba(255, 247, 230, .18);
transform: translateX(-50%);
}
}
.ts05-row {
display: flex;
&--l { justify-content: flex-start; }
&--r { justify-content: flex-end; }
}
.ts05-card {
width: 44%;
box-sizing: border-box;
padding: $sp-2 $sp-3;
border-radius: $r-sm;
background: $color;
color: $subject-cream;
font: 700 12px/1 -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
opacity: 0;
}
// 실제 사용 코드: 뷰포트에 들어오면 홀수는 왼쪽, 짝수는 오른쪽에서 모인다
.ts05-row--l .ts05-card { transform: translateX(-24px); }
.ts05-row--r .ts05-card { transform: translateX(24px); }
@supports (animation-timeline: view()) {
.ts05-row--l .ts05-card { animation: ts05-in-l $duration $easing both; animation-timeline: view(); animation-range: entry 0% cover 60%; }
.ts05-row--r .ts05-card { animation: ts05-in-r $duration $easing both; animation-timeline: view(); animation-range: entry 0% cover 60%; }
}
@supports not (animation-timeline: view()) {
.ts05-card { opacity: 1; transform: none; }
}
@keyframes ts05-in-l { from { opacity: 0; transform: translateX(-24px); } to { opacity: 1; transform: translateX(0); } }
@keyframes ts05-in-r { from { opacity: 0; transform: translateX(24px); } to { opacity: 1; transform: translateX(0); } }
// 데모 루프: 위에서부터 순서대로
.ts05-timeline.is-demo .ts05-row--l .ts05-card { animation: ts05-in-l $duration $easing infinite; }
.ts05-timeline.is-demo .ts05-row--r .ts05-card { animation: ts05-in-r $duration $easing infinite; animation-delay: .18s; }
.ts05-timeline.is-demo .ts05-row:nth-child(3) .ts05-card { animation-delay: .36s; }
@media (prefers-reduced-motion: reduce) {
.ts05-card, .ts05-timeline.is-demo .ts05-card { animation: none !important; opacity: 1 !important; transform: none !important; }
}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);
}
.ts05-timeline {
position: relative;
width: min(280px, 84vw);
display: flex;
flex-direction: column;
gap: 16px;
}
.ts05-timeline::before {
content: "";
position: absolute;
left: 50%;
top: 0;
width: 2px;
height: 100%;
background: rgba(255, 247, 230, 0.18);
transform: translateX(-50%);
}
.ts05-row {
display: flex;
}
.ts05-row--l {
justify-content: flex-start;
}
.ts05-row--r {
justify-content: flex-end;
}
.ts05-card {
width: 44%;
box-sizing: border-box;
padding: 8px 12px;
border-radius: 8px;
background: #0a4fe8;
color: #fff7e6;
font: 700 12px/1 -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
opacity: 0;
}
.ts05-row--l .ts05-card {
transform: translateX(-24px);
}
.ts05-row--r .ts05-card {
transform: translateX(24px);
}
@supports (animation-timeline: view()) {
.ts05-row--l .ts05-card {
animation: ts05-in-l 2s cubic-bezier(0.2, 0.8, 0.2, 1) both;
animation-timeline: view();
animation-range: entry 0% cover 60%;
}
.ts05-row--r .ts05-card {
animation: ts05-in-r 2s cubic-bezier(0.2, 0.8, 0.2, 1) both;
animation-timeline: view();
animation-range: entry 0% cover 60%;
}
}
@supports not (animation-timeline: view()) {
.ts05-card {
opacity: 1;
transform: none;
}
}
@keyframes ts05-in-l {
from {
opacity: 0;
transform: translateX(-24px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes ts05-in-r {
from {
opacity: 0;
transform: translateX(24px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.ts05-timeline.is-demo .ts05-row--l .ts05-card {
animation: ts05-in-l 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.ts05-timeline.is-demo .ts05-row--r .ts05-card {
animation: ts05-in-r 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
animation-delay: 0.18s;
}
.ts05-timeline.is-demo .ts05-row:nth-child(3) .ts05-card {
animation-delay: 0.36s;
}
@media (prefers-reduced-motion: reduce) {
.ts05-card, .ts05-timeline.is-demo .ts05-card {
animation: none !important;
opacity: 1 !important;
transform: none !important;
}
}import { CSSProperties } from "react";
import styles from "./AlternatingCardSlide.module.scss";
type Vars = CSSProperties & {
"--duration"?: string;
"--easing"?: string;
"--color"?: string;
};
export interface AlternatingCardSlideProps {
duration?: string;
easing?: string;
color?: string;
dates?: string[];
}
export default function AlternatingCardSlide({
duration = "2s",
easing = "cubic-bezier(.2, .8, .2, 1)",
color = "#0a4fe8",
dates = ["2024.01", "2024.06", "2024.12"],
}: AlternatingCardSlideProps) {
const style: Vars = { "--duration": duration, "--easing": easing, "--color": color };
return (
<div className={styles.timeline} style={style}>
{dates.map((d, i) => (
<div key={d} className={`${styles.row} ${i % 2 === 0 ? styles.rowL : styles.rowR}`}>
<div className={styles.card} style={{ animationDelay: `${i * 180}ms` }}>{d}</div>
</div>
))}
</div>
);
}05 Card trượt so le hai bên — Diện tích đo được 8.283% · khung hình chuyển động 23/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · 9 css timeline animation không JS, copy-paste
Bài viết chứa demo này
9 css timeline animation không JS, copy-pasteCss timeline animation là cách nối nhiều bước bằng đường kẻ với vòng tròn để bày ra theo thứ tự. Chín mẫu dưới đây không Xem tất cả demo trong bài này


