06Dải marquee chạy ngang
Hàng pill tích hợp dưới hero nhân đôi rồi cuộn vô tận bằng translateX(-50%), khoảng cách lấy bằng margin-right thay cho flex gap nên mối nối vòng lặp không hề giật.
Dùng choDải logo tích hợp·đối tác, hàng social proof dưới hero
translateX(-50%)margin-right gapdoubled track

<main class="stage">
<div class="marquee">
<p class="marquee__label">Integrates with</p>
<div class="marquee__row">
<div class="marquee__track">
<span class="marquee__pill">STRIPE</span>
<span class="marquee__pill">NOTION</span>
<span class="marquee__pill">FIGMA</span>
<span class="marquee__pill">SLACK</span>
<span class="marquee__pill">LINEAR</span>
<span class="marquee__pill" aria-hidden="true">STRIPE</span>
<span class="marquee__pill" aria-hidden="true">NOTION</span>
<span class="marquee__pill" aria-hidden="true">FIGMA</span>
<span class="marquee__pill" aria-hidden="true">SLACK</span>
<span class="marquee__pill" aria-hidden="true">LINEAR</span>
</div>
</div>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────
$duration: $dur-loop; // 2s — 트랙 한 바퀴(-50%)
$easing: linear;
$color: $stage-ink;
@include stage($stage-yellow);
.marquee { width: min(340px, 88vw); display: grid; gap: $sp-2; justify-items: center; }
.marquee__label {
margin: 0;
font-weight: 700;
font-size: clamp(11px, 3vw, 12px);
line-height: 1;
font-family: inherit;
letter-spacing: .08em;
color: rgba($color, .75);
text-transform: uppercase;
}
.marquee__row { width: 100%; overflow: hidden; }
.marquee__track {
display: flex;
width: max-content;
animation: pill-scroll $duration $easing infinite;
}
.marquee__pill {
margin-right: $sp-4; // 트랙의 gap 대신 — 세트 폭이 정확히 절반이 되어 -50% 이음새가 0px 이 된다 (BD15)
flex-shrink: 0;
display: grid;
place-items: center;
height: $sp-8;
padding: 0 $sp-4;
border-radius: $r-pill;
background: $stage-paper;
color: $color;
font-weight: 800;
font-size: 13px;
line-height: 1;
font-family: inherit;
letter-spacing: .04em;
box-shadow: $shadow-press;
}
@keyframes pill-scroll {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
@media (prefers-reduced-motion: reduce) {
.marquee__track { animation: none; transform: translateX(0); }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ffd23f;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.marquee {
width: min(340px, 88vw);
display: grid;
gap: 8px;
justify-items: center;
}
.marquee__label {
margin: 0;
font-weight: 700;
font-size: clamp(11px, 3vw, 12px);
line-height: 1;
font-family: inherit;
letter-spacing: 0.08em;
color: rgba(23, 20, 26, 0.75);
text-transform: uppercase;
}
.marquee__row {
width: 100%;
overflow: hidden;
}
.marquee__track {
display: flex;
width: max-content;
animation: pill-scroll 2s linear infinite;
}
.marquee__pill {
margin-right: 16px;
flex-shrink: 0;
display: grid;
place-items: center;
height: 32px;
padding: 0 16px;
border-radius: 999px;
background: #ffffff;
color: #17141a;
font-weight: 800;
font-size: 13px;
line-height: 1;
font-family: inherit;
letter-spacing: 0.04em;
box-shadow: 0 6px 12px rgba(23, 20, 26, 0.32);
}
@keyframes pill-scroll {
from {
transform: translateX(0);
}
to {
transform: translateX(-50%);
}
}
@media (prefers-reduced-motion: reduce) {
.marquee__track {
animation: none;
transform: translateX(0);
}
}import type { CSSProperties } from "react";
import styles from "./MarqueeStrip.module.scss";
export type MarqueeStripProps = {
label?: string;
pills?: string[];
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function MarqueeStrip({
label = "Integrates with",
pills = ["STRIPE", "NOTION", "FIGMA", "SLACK", "LINEAR"],
duration = 2000,
easing = "linear",
color = "#17141a",
}: MarqueeStripProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<div className={styles.marquee} style={vars}>
<p className={styles.label}>{label}</p>
<div className={styles.row}>
<div className={styles.track}>
{pills.map((p) => (
<span key={p} className={styles.pill}>{p}</span>
))}
{pills.map((p) => (
<span key={"dup-" + p} className={styles.pill} aria-hidden="true">{p}</span>
))}
</div>
</div>
</div>
);
}06 Dải marquee chạy ngang — Diện tích đo được 7.556% · khung hình chuyển động 23/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · 9 hero section css animation — không cần JS
Bài viết chứa demo này
9 hero section css animation — không cần JSHero section là màn hình lớn đập vào mắt đầu tiên khi bạn vừa vào một trang web, nơi đặt tiêu đề cỡ đại và nút bấm dựng Xem tất cả demo trong bài này


