01Ba chấm đang suy nghĩ
Trong lúc AI soạn câu trả lời, ba chấm nhỏ tại ô tin nhắn to lên rồi nhỏ xuống theo trình tự, đưa tín hiệu chờ tự nhiên như một người đang gõ chữ.
Dùng choChỉ báo chờ trả lời chatbot, trạng thái loading chat hỗ trợ khách hàng
scale@keyframes1.2s

<main class="stage">
<div class="td-bubble is-demo">
<span class="td-dot"></span>
<span class="td-dot"></span>
<span class="td-dot"></span>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────
$duration: 1.2s;
$easing: $ease-out;
$color: $subject-cream;
@include stage($stage-ink);
.td-bubble {
display: inline-flex;
align-items: center;
gap: $sp-2;
padding: $sp-4 $sp-5;
border-radius: $r-card;
background: rgba(255, 255, 255, .08);
}
.td-dot {
width: $sp-2;
height: $sp-2;
border-radius: $r-pill;
background: $color;
opacity: .35;
}
// 실사용: 로딩 시작하면 JS 가 .is-loading 을 붙인다
@keyframes td-bounce {
0%, 60%, 100% { transform: translateY(0); opacity: .35; }
30% { transform: translateY(-6px); opacity: 1; }
}
.td-bubble.is-loading .td-dot,
.td-bubble.is-demo .td-dot {
animation: td-bounce $duration $easing infinite;
}
.td-bubble.is-demo .td-dot:nth-child(1) { animation-delay: 0ms; }
.td-bubble.is-demo .td-dot:nth-child(2) { animation-delay: 160ms; }
.td-bubble.is-demo .td-dot:nth-child(3) { animation-delay: 320ms; }
@media (prefers-reduced-motion: reduce) {
.td-bubble.is-loading .td-dot,
.td-bubble.is-demo .td-dot {
animation: none !important;
opacity: .8 !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);
}
.td-bubble {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 16px 20px;
border-radius: 16px;
background: rgba(255, 255, 255, 0.08);
}
.td-dot {
width: 8px;
height: 8px;
border-radius: 999px;
background: #fff7e6;
opacity: 0.35;
}
@keyframes td-bounce {
0%, 60%, 100% {
transform: translateY(0);
opacity: 0.35;
}
30% {
transform: translateY(-6px);
opacity: 1;
}
}
.td-bubble.is-loading .td-dot,
.td-bubble.is-demo .td-dot {
animation: td-bounce 1.2s ease-out infinite;
}
.td-bubble.is-demo .td-dot:nth-child(1) {
animation-delay: 0ms;
}
.td-bubble.is-demo .td-dot:nth-child(2) {
animation-delay: 160ms;
}
.td-bubble.is-demo .td-dot:nth-child(3) {
animation-delay: 320ms;
}
@media (prefers-reduced-motion: reduce) {
.td-bubble.is-loading .td-dot,
.td-bubble.is-demo .td-dot {
animation: none !important;
opacity: 0.8 !important;
transform: none !important;
}
}import { CSSProperties } from "react";
import styles from "./ThinkingDots.module.scss";
type Vars = CSSProperties & {
"--duration"?: string;
"--easing"?: string;
"--color"?: string;
};
export interface ThinkingDotsProps {
duration?: string;
easing?: string;
color?: string;
}
export default function ThinkingDots({
duration = "1.2s",
easing = "ease-out",
color = "#fff7e6",
}: ThinkingDotsProps) {
const style: Vars = {
"--duration": duration,
"--easing": easing,
"--color": color,
};
return (
<div className={styles.bubble} style={style}>
<span className={styles.dot} />
<span className={styles.dot} />
<span className={styles.dot} />
</div>
);
}01 Ba chấm đang suy nghĩ — Diện tích đo được 0.225% · khung hình chuyển động 21/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · 9 css loading animation cho AI chat, copy-paste
Bài viết chứa demo này
9 css loading animation cho AI chat, copy-pasteBa chấm nhấp nhô, con trỏ chớp tắt, chữ hiện dần lúc bạn chờ câu trả lời trong khung chat AI như ChatGPT hay Claude đều Xem tất cả demo trong bài này


