04Skeleton bảng
Giữ nguyên header, chỉ biến các ô thành thanh.
Dùng choBảng trong dashboard
6행열 폭 유지줄무늬

<main class="stage">
<div class="table">
<div class="row head"><span>Tên</span><span>Trạng thái</span><span>Giá trị</span><span>Ngày</span></div>
<div class="body">
<div class="row"><span class="cell"></span><span class="cell"></span><span class="cell"></span><span class="cell"></span></div>
<div class="row"><span class="cell"></span><span class="cell"></span><span class="cell"></span><span class="cell"></span></div>
<div class="row"><span class="cell"></span><span class="cell"></span><span class="cell"></span><span class="cell"></span></div>
<div class="row"><span class="cell"></span><span class="cell"></span><span class="cell"></span><span class="cell"></span></div>
<div class="row"><span class="cell"></span><span class="cell"></span><span class="cell"></span><span class="cell"></span></div>
<div class="row"><span class="cell"></span><span class="cell"></span><span class="cell"></span><span class="cell"></span></div>
<span class="sheen" aria-hidden="true"></span>
</div>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────
$duration: $dur-loop; // 빛이 표 몸통을 훑는 시간
$easing: $ease-out;
$color: $subject-ink; // 셀 색 (흰 무대라 잉크 톤)
@include stage($stage-paper);
.table {
width: clamp(220px, 88%, 380px);
border-radius: $r-card;
overflow: hidden;
background: rgba($color, .03);
box-shadow: $shadow-press;
}
// 열 폭은 헤더와 몸통이 같은 grid-template-columns 를 써서 유지한다
.row {
display: grid;
grid-template-columns: 2fr 1fr 1fr 1fr;
gap: clamp(6px, 2vw, 10px);
padding: clamp(6px, 2vw, 9px) clamp(10px, 3vw, 14px);
align-items: center;
}
.row.head {
font: 600 clamp(10px, 2.8vw, 12px)/1.2 -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
color: rgba($color, .7);
border-bottom: 1px solid rgba($color, .12);
}
.body {
position: relative; // .sheen 의 위치 기준 (빠지면 엉뚱한 곳에 그려진다)
overflow: hidden; // 빛이 몸통 밖(헤더 포함)으로 안 나가게
}
.body .row:nth-child(even) { background: rgba($color, .03); }
.cell {
height: clamp(9px, 2.6vw, 11px);
border-radius: $r-control;
background: rgba($color, .13);
}
// ── 빛 한 줄기가 몸통만 훑는다. 헤더 글자는 그대로 둔다 ──────────
.sheen {
position: absolute;
inset: 0;
background: linear-gradient(90deg, transparent, rgba($subject-blue, .45), transparent);
transform: translateX(-120%);
animation: shimmer $duration $easing infinite;
pointer-events: none;
}
@keyframes shimmer {
to { transform: translateX(120%); }
}
@media (prefers-reduced-motion: reduce) {
.sheen { animation: none !important; opacity: 0; }
}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);
}
.table {
width: clamp(220px, 88%, 380px);
border-radius: 16px;
overflow: hidden;
background: rgba(23, 20, 26, 0.03);
box-shadow: 0 6px 12px rgba(23, 20, 26, 0.32);
}
.row {
display: grid;
grid-template-columns: 2fr 1fr 1fr 1fr;
gap: clamp(6px, 2vw, 10px);
padding: clamp(6px, 2vw, 9px) clamp(10px, 3vw, 14px);
align-items: center;
}
.row.head {
font: 600 clamp(10px, 2.8vw, 12px)/1.2 -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
color: rgba(23, 20, 26, 0.7);
border-bottom: 1px solid rgba(23, 20, 26, 0.12);
}
.body {
position: relative;
overflow: hidden;
}
.body .row:nth-child(even) {
background: rgba(23, 20, 26, 0.03);
}
.cell {
height: clamp(9px, 2.6vw, 11px);
border-radius: 12px;
background: rgba(23, 20, 26, 0.13);
}
.sheen {
position: absolute;
inset: 0;
background: linear-gradient(90deg, transparent, rgba(47, 109, 246, 0.45), transparent);
transform: translateX(-120%);
animation: shimmer 2s ease-out infinite;
pointer-events: none;
}
@keyframes shimmer {
to {
transform: translateX(120%);
}
}
@media (prefers-reduced-motion: reduce) {
.sheen {
animation: none !important;
opacity: 0;
}
}import type { CSSProperties } from "react";
import styles from "./TableSkeleton.module.scss";
export type TableSkeletonProps = {
/** 빛이 표 몸통을 훑는 시간 (기본 2000ms) */
duration?: number;
/** CSS easing (기본 ease-out) */
easing?: string;
/** 셀 색 */
color?: string;
/** 머리글 (열 폭은 이 배열 길이로 정해진다) */
columns?: string[];
/** 몸통 행 수 (기본 6) */
rows?: number;
className?: string;
};
export default function TableSkeleton({
duration = 2000,
easing = "ease-out",
color = "#17141a",
columns = ["이름", "상태", "값", "날짜"],
rows = 6,
className = "",
}: TableSkeletonProps) {
const vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color } as CSSProperties;
const template = { gridTemplateColumns: `2fr repeat(${columns.length - 1}, 1fr)` } as CSSProperties;
return (
<div className={`${styles.table} ${className}`} style={vars}>
<div className={`${styles.row} ${styles.head}`} style={template}>
{columns.map((c) => <span key={c}>{c}</span>)}
</div>
<div className={styles.body}>
{Array.from({ length: rows }).map((_, r) => (
<div key={r} className={styles.row} style={template}>
{columns.map((_, c) => <span key={c} className={styles.cell} />)}
</div>
))}
<span className={styles.sheen} aria-hidden="true" />
</div>
</div>
);
}04 Skeleton bảng — Diện tích đo được 45.848% · khung hình chuyển động 17/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · 9 hiệu ứng css skeleton loading copy-paste
Bài viết chứa demo này
9 hiệu ứng css skeleton loading copy-pasteCss skeleton loading là kỹ thuật hiện khối xám giữ đúng hình dạng của nội dung thật trong lúc chờ dữ liệu, thay cho màn Xem tất cả demo trong bài này


