08색종이 띠 푸터
3열 링크 아래에 네 색 조각을 두 벌 넣은 띠가 한 벌만큼만 흘러, 한 바퀴가 끝나도 이음매가 보이지 않습니다.
쓰임밝은 톤 사이트의 마지막 띠
두 벌 복제margin-right 간격translateX(-50%)

<main class="stage">
<footer class="pfoot">
<div class="pfoot__cols">
<div class="pfoot__col">
<p class="pfoot__h">공작실</p>
<p class="pfoot__l">소개</p>
<p class="pfoot__l">기록</p>
</div>
<div class="pfoot__col">
<p class="pfoot__h">재료</p>
<p class="pfoot__l">색종이</p>
<p class="pfoot__l">스티커</p>
</div>
<div class="pfoot__col">
<p class="pfoot__h">도움말</p>
<p class="pfoot__l">문의</p>
<p class="pfoot__l">약관</p>
</div>
</div>
<div class="pfoot__strip" aria-hidden="true">
<div class="pfoot__track">
<span class="pfoot__bit pfoot__bit--coral"></span>
<span class="pfoot__bit pfoot__bit--butter"></span>
<span class="pfoot__bit pfoot__bit--leaf"></span>
<span class="pfoot__bit pfoot__bit--sky"></span>
<span class="pfoot__bit pfoot__bit--coral"></span>
<span class="pfoot__bit pfoot__bit--butter"></span>
<span class="pfoot__bit pfoot__bit--leaf"></span>
<span class="pfoot__bit pfoot__bit--sky"></span>
</div>
</div>
</footer>
</main>@use "../../../../site/static/demo-tokens" as *;
// 페이퍼컷 팔레트
$paper-base: #fbf6ec;
$paper-sheet: #ffffff;
$paper-ink: #2a2320;
$paper-stone: #5c5048;
$paper-coral: #b97b71;
$paper-sky: #6a8db5;
$paper-leaf: #2b7d5a;
$paper-butter: #d6c9ab;
$paper-edge: #e7dccb;
$duration: $dur-loop; // 색종이 띠가 한 벌만큼 흘러가는 주기
$easing: linear; // 흐름은 일정 속도라야 이음매가 안 보인다
$color: $paper-edge; // 푸터 종이의 아래 두께
@include stage($paper-butter);
.pfoot {
box-sizing: border-box;
width: clamp(264px, 86vw, 388px);
padding: $sp-5 $sp-5 0;
border-radius: $r-card;
background: $paper-sheet;
box-shadow: 0 6px 0 $color;
overflow: hidden;
}
.pfoot__cols {
display: flex;
gap: $sp-6;
margin-bottom: $sp-4;
}
.pfoot__col { flex: 1 1 0; }
.pfoot__h {
margin: 0 0 $sp-2;
color: $paper-ink;
font-weight: 800; font-size: 12px; line-height: 1;
font-family: inherit;
}
.pfoot__l {
margin: 0 0 $sp-1;
color: $paper-stone;
font-weight: 500; font-size: 11px; line-height: 1.3;
font-family: inherit;
}
// 아래 색종이 띠 — 같은 네 조각을 두 벌 넣고 한 벌만큼(-50%) 민다. 끝에서 처음으로 튀는 순간이 없다
.pfoot__strip {
overflow: hidden;
margin: 0 #{-$sp-5};
padding: $sp-2 0;
background: $paper-base;
}
.pfoot__track {
display: flex;
width: max-content;
animation: stripFlow $duration $easing infinite;
}
.pfoot__bit {
flex: 0 0 auto;
width: 36px;
height: $sp-5;
margin-right: $sp-3; // 간격은 gap 이 아니라 margin — 2N 조각 사이 gap 은 2N-1 개라 -50% 가 어긋난다
border-radius: $r-xs;
}
.pfoot__bit--coral { background: $paper-coral; }
.pfoot__bit--butter { background: $paper-butter; }
.pfoot__bit--leaf { background: $paper-leaf; }
.pfoot__bit--sky { background: $paper-sky; }
// 데모: 트랙 전체가 정확히 한 벌(=전체 폭의 50%)만큼 왼쪽으로 흐른 뒤 처음 상태와 완전히 같아진다
@keyframes stripFlow {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
@media (max-width: 340px), (max-height: 220px) {
.pfoot { width: 276px; padding: $sp-3 $sp-3 0; }
.pfoot__cols { gap: $sp-3; margin-bottom: $sp-2; }
.pfoot__h { font-size: 10px; margin-bottom: $sp-1; }
.pfoot__l { font-size: 9px; }
.pfoot__strip { margin: 0 #{-$sp-3}; padding: $sp-1 0; }
.pfoot__bit { width: 24px; height: $sp-4; margin-right: $sp-2; }
}
@media (prefers-reduced-motion: reduce) {
.pfoot__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: #d6c9ab;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.pfoot {
box-sizing: border-box;
width: clamp(264px, 86vw, 388px);
padding: 20px 20px 0;
border-radius: 16px;
background: #ffffff;
box-shadow: 0 6px 0 #e7dccb;
overflow: hidden;
}
.pfoot__cols {
display: flex;
gap: 24px;
margin-bottom: 16px;
}
.pfoot__col {
flex: 1 1 0;
}
.pfoot__h {
margin: 0 0 8px;
color: #2a2320;
font-weight: 800;
font-size: 12px;
line-height: 1;
font-family: inherit;
}
.pfoot__l {
margin: 0 0 4px;
color: #5c5048;
font-weight: 500;
font-size: 11px;
line-height: 1.3;
font-family: inherit;
}
.pfoot__strip {
overflow: hidden;
margin: 0 -20px;
padding: 8px 0;
background: #fbf6ec;
}
.pfoot__track {
display: flex;
width: max-content;
animation: stripFlow 2s linear infinite;
}
.pfoot__bit {
flex: 0 0 auto;
width: 36px;
height: 20px;
margin-right: 12px;
border-radius: 4px;
}
.pfoot__bit--coral {
background: #b97b71;
}
.pfoot__bit--butter {
background: #d6c9ab;
}
.pfoot__bit--leaf {
background: #2b7d5a;
}
.pfoot__bit--sky {
background: #6a8db5;
}
@keyframes stripFlow {
from {
transform: translateX(0);
}
to {
transform: translateX(-50%);
}
}
@media (max-width: 340px), (max-height: 220px) {
.pfoot {
width: 276px;
padding: 12px 12px 0;
}
.pfoot__cols {
gap: 12px;
margin-bottom: 8px;
}
.pfoot__h {
font-size: 10px;
margin-bottom: 4px;
}
.pfoot__l {
font-size: 9px;
}
.pfoot__strip {
margin: 0 -12px;
padding: 4px 0;
}
.pfoot__bit {
width: 24px;
height: 16px;
margin-right: 8px;
}
}
@media (prefers-reduced-motion: reduce) {
.pfoot__track {
animation: none !important;
transform: none;
}
}import type { CSSProperties } from "react";
import styles from "./PaperFooter.module.scss";
export type FooterColumn = { head: string; links: string[] };
export type PaperFooterProps = {
columns?: FooterColumn[];
/** 띠에 놓을 색종이 색 한 벌. 컴포넌트가 두 벌로 복제한다 */
chips?: string[];
/** 한 벌만큼 흐르는 시간(기본 2000ms) */
duration?: number;
};
type Vars = CSSProperties & { "--duration"?: string };
export default function PaperFooter({
columns = [
{ head: "공작실", links: ["소개", "기록"] },
{ head: "재료", links: ["색종이", "스티커"] },
{ head: "도움말", links: ["문의", "약관"] },
],
chips = ["#cc3d26", "#f6c453", "#2b7d5a", "#2f6fb8"],
duration = 2000,
}: PaperFooterProps) {
const vars: Vars = { "--duration": `${duration}ms` };
// 한 벌을 그대로 두 번 깔아야 -50% 지점이 첫 프레임과 완전히 같아진다 (이음매 없음)
const track = [...chips, ...chips];
return (
<footer className={styles.pfoot} style={vars}>
<div className={styles.cols}>
{columns.map((c) => (
<div key={c.head} className={styles.col}>
<p className={styles.h}>{c.head}</p>
{c.links.map((l) => <p key={l} className={styles.l}>{l}</p>)}
</div>
))}
</div>
<div className={styles.strip} aria-hidden="true">
<div className={styles.track}>
{track.map((c, i) => <span key={`${c}-${i}`} className={styles.bit} style={{ background: c }} />)}
</div>
</div>
</footer>
);
}08 색종이 띠 푸터 — 측정 누적면적 5.163% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 종이 질감 디자인 랜딩 키트, 오려 붙인 섹션 9개
이 데모가 속한 글
종이 질감 디자인 랜딩 키트, 오려 붙인 섹션 9개종이 질감 디자인은 색종이를 가위로 오려 붙인 것처럼 면마다 흐림 없는 그림자로 두께를 주고, 색은 다섯 가지 안에서만 쓰는 스타일입니다. 종이 사진을 깔지 않고 그림자 값 하나만으로 종이처럼 보이게 만드는 것이 전 이 글의 데모 전부 보기


