06Icon trạng thái rỗng nảy nhẹ
Icon hiện ra khi danh sách rỗng tự nảy nhẹ lên xuống, báo màn hình trống là chủ ý chứ không phải lỗi.
Dùng choMàn hình không có kết quả tìm kiếm, giỏ hàng rỗng
translateY 바운스ease-out 이징무한 반복

<main class="stage">
<div class="empty is-demo">
<span class="empty__icon" aria-hidden="true">
<span class="empty__tray"></span>
<span class="empty__divider"></span>
</span>
<p class="empty__text">Không có kết quả</p>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────
$duration: $dur-loop; // 바운스 한 주기 시간
$easing: $ease-out; // 위아래로 부드럽게
$color: $stage-ink; // 아이콘 색
// ── 데모 무대 ──────────────────────────────────────────
@include stage($stage-yellow);
.empty {
display: flex;
flex-direction: column;
align-items: center;
gap: clamp(10px, 3vw, 14px);
color: $color;
&__icon {
position: relative;
width: 52px;
height: 40px;
transform: translateY(0);
}
&__tray {
position: absolute; inset: 0;
border: 3px solid currentColor;
border-radius: $r-control;
}
&__divider {
position: absolute;
left: 3px; right: 3px; top: 13px;
height: 0;
border-top: 3px solid currentColor;
}
&__text {
margin: 0;
font-weight: 700;
font-size: clamp(13px, 3.8vw, 15px);
line-height: 1.3;
font-family: inherit;
}
&.is-demo &__icon { animation: bounce $duration $easing infinite; }
}
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
// ── 접근성: 움직임 줄이기 설정이면 정지 ────────────────────
@media (prefers-reduced-motion: reduce) {
.empty.is-demo .empty__icon { animation: none !important; }
}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);
}
.empty {
display: flex;
flex-direction: column;
align-items: center;
gap: clamp(10px, 3vw, 14px);
color: #17141a;
}
.empty__icon {
position: relative;
width: 52px;
height: 40px;
transform: translateY(0);
}
.empty__tray {
position: absolute;
inset: 0;
border: 3px solid currentColor;
border-radius: 12px;
}
.empty__divider {
position: absolute;
left: 3px;
right: 3px;
top: 13px;
height: 0;
border-top: 3px solid currentColor;
}
.empty__text {
margin: 0;
font-weight: 700;
font-size: clamp(13px, 3.8vw, 15px);
line-height: 1.3;
font-family: inherit;
}
.empty.is-demo .empty__icon {
animation: bounce 2s ease-out infinite;
}
@keyframes bounce {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-10px);
}
}
@media (prefers-reduced-motion: reduce) {
.empty.is-demo .empty__icon {
animation: none !important;
}
}import type { CSSProperties } from "react";
import styles from "./EmptyStateBounce.module.scss";
export type EmptyStateBounceProps = {
text: string;
/** 바운스 한 주기 시간 (기본 2000ms) */
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function EmptyStateBounce({
text,
duration = 2000,
easing = "ease-out",
color = "#17141a",
}: EmptyStateBounceProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<div className={styles.empty} style={vars}>
<span className={styles.icon} aria-hidden="true">
<span className={styles.tray} />
<span className={styles.divider} />
</span>
<p className={styles.text}>{text}</p>
</div>
);
}06 Icon trạng thái rỗng nảy nhẹ — Diện tích đo được 1.361% · khung hình chuyển động 23/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · 9 micro interactions css — dán 1 dòng chạy ngay
Bài viết chứa demo này
9 micro interactions css — dán 1 dòng chạy ngayMicro interactions css là những phản hồi cực nhỏ trên giao diện — nút lún xuống, icon đổi hình, toast xếp chồng — báo ch Xem tất cả demo trong bài này

