在大型語言模型(LLM)的應用場景中,從使用者按下送出到模型完成多模態推論與結構化回傳,通常存在約 2~3 秒的運算延遲。如果這段期間介面毫無反饋,或者只有一顆生硬旋轉的 Spinner,使用者很容易產生「系統是不是卡死、當機了?」的焦慮感。
今天我們將專注於 使用者體驗(UX)的細節打磨,為《喵語日誌》打造專屬的貓咪微光骨架屏(Skeleton UI),搭配動態輪播文案與全域防重複提交機制,把原本枯燥的等待轉化為有溫度的陪伴互動!
在打造 AI 驅動的產品時,載入體驗的好壞直接影響使用者的留存意願:
消除操作不確定感: 透過預先佔位的骨架氣泡,明確告訴使用者「系統已確實收到訊息,貓咪正在組織語言」。
維持介面結構穩定: Skeleton 預先模擬了真實對話氣泡的高度與結構,避免資料回傳瞬間產生突兀的版面跳動(Layout Shift)。
增加趣味微互動: 在等待期間輪播隨機的貓咪碎碎念,提升產品的情緒價值與品牌陪伴感。
在全域樣式檔 src/index.css 中定義線性漸層掃過動畫與三點交錯縮放的 Keyframes:
@import "tailwindcss";
/* 🎯 Day 26: 貓咪骨架屏微光 (Shimmer) 與跳動動畫 */
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
.animate-shimmer {
background: linear-gradient(
90deg,
rgba(255, 228, 230, 0.4) 0%,
rgba(255, 255, 255, 0.8) 50%,
rgba(255, 228, 230, 0.4) 100%
);
background-size: 200% 100%;
animation: shimmer 1.6s infinite linear;
}
@keyframes bounce-delay {
0%, 80%, 100% { transform: scale(0.6); opacity: 0.35; }
40% { transform: scale(1); opacity: 1; }
}
.animate-dot-1 { animation: bounce-delay 1.4s infinite ease-in-out 0s; }
.animate-dot-2 { animation: bounce-delay 1.4s infinite ease-in-out 0.2s; }
.animate-dot-3 { animation: bounce-delay 1.4s infinite ease-in-out 0.4s; }
在 ChatContainer.tsx 中封裝 CatSkeletonLoader。我們設定 setInterval 每 1.8 秒自動輪換等待文案,並呈現帶有 Shimmer 效果的粉嫩佔位長條:
const WAITING_TEXTS = [
"貓咪正在認真思考中...🐾",
"正在聞這段日記的味道...🐟",
"喵喵正在組織可愛的語言...✨",
"貓咪搖了搖尾巴,馬上就好囉...🌿",
]
function CatSkeletonLoader() {
const [textIndex, setTextIndex] = useState(0)
useEffect(() => {
const timer = setInterval(() => {
setTextIndex((prev) => (prev + 1) % WAITING_TEXTS.length)
}, 1800)
return () => clearInterval(timer)
}, [])
return (
<div className="flex items-start gap-2.5 transition-all duration-300 ease-out animate-in fade-in slide-in-from-bottom-2">
<CatAvatar size="sm" />
<div className="max-w-[85%] sm:max-w-[75%] rounded-2xl rounded-tl-sm bg-rose-50/90 border border-rose-200/70 p-3.5 shadow-sm space-y-2.5">
<div className="flex items-center gap-2 text-xs text-rose-500 font-medium">
<span>{WAITING_TEXTS[textIndex]}</span>
<div className="flex gap-1 items-center">
<span className="h-1.5 w-1.5 rounded-full bg-rose-400 animate-dot-1 inline-block" />
<span className="h-1.5 w-1.5 rounded-full bg-rose-400 animate-dot-2 inline-block" />
<span className="h-1.5 w-1.5 rounded-full bg-rose-400 animate-dot-3 inline-block" />
</div>
</div>
<div className="h-3 w-44 sm:w-56 rounded-md animate-shimmer" />
<div className="h-3 w-28 sm:w-36 rounded-md animate-shimmer" />
</div>
</div>
)
}
發送請求後,將輸入框、圖片選擇鈕、心情選擇器及送出按鈕統一加上 disabled={isLoading} 屬性,避免使用者因為連點造成多次請求、額外消耗 API 配額或導致狀態錯亂:
<input
type="text"
value={input}
disabled={isLoading}
onChange={(e) => setInput(e.target.value)}
onKeyDown={handleKeyDown}
placeholder={isLoading ? "貓咪正在回覆中,請稍候..." : "跟喵喵說點什麼吧…"}
className="... disabled:bg-stone-100 disabled:text-stone-400 disabled:cursor-not-allowed"
/>
<button
type="button"
onClick={handleSend}
disabled={(!input.trim() && !selectedImage) || isLoading}
className="... disabled:opacity-40 disabled:cursor-not-allowed"
>
<Send className="h-4 w-4" />
</button>
完成更新並推送至 GitHub 後,Vercel 觸發自動建置完成:
等待狀態:按下送出瞬間,輸入框立即鎖定並切換 Placeholder 提示「貓咪正在回覆中...」,對話列表下方流暢滑入帶有動態文案與 Shimmer 微光的粉紅貓咪氣泡。
回覆呈現:API 資料回傳後,骨架屏無縫切換為正式的貓咪管家卡片,所有輸入控制器同步恢復為可操作狀態。

📷 送出訊息後,畫面上出現帶有微光掃過效果的貓咪骨架屏與輪播文案。
在功能開發初期,我們通常最在意的是「API 能不能通」與「資料能不能存」;但真正決定一款產品成熟度的,往往是這些看似微小卻極其關鍵的 UX 細節。
透過 Skeleton UI 與微互動動畫,不僅有效緩解了 LLM 推論延遲帶來的空白卡頓感,更強化了《喵語日誌》本身的療癒陪伴感。好的載入狀態設計不是單純掩飾延遲,而是把等待轉化為品牌個性的一部分!
今天我們成功補齊了 AI 應用的載入體驗,使整體的互動手感更加流暢完整。
進入鐵人賽的最後倒數!明天我們將把部署好的網址交給真實使用者試玩,並根據第一手的操作反饋,針對行動端響應式排版(RWD)、手機鍵盤適配與互動細節進行敏捷調優,敬請期待!
明天見,喵~ 🐾