Frontend Lab
← 回到所有模式
PF02💬 Feedback · 反饋進度

Progress

進度條

難度 ★★★★★標籤 progress · determinate · indeterminate3 min read

模式簡述

Progress:任務進度。determinate 顯示百分比;indeterminate 顯示「正在做」。circular vs linear 選對情境。

上傳中⋯45%
進度條|線性進度 — 65%

Claude Code Prompt

💬 Claude Code Prompt
參考:PF02 progress
組件:Progress.tsx

Progress:任務進度。determinate 顯示百分比;indeterminate 顯示「正在做」。circular vs linear 選對情境。

限制:
- 進度回跳
- indeterminate 動畫太慢
- 到 100% 沒給結束反饋

完整程式碼

Progress.tsx
"use client";
import { useEffect, useState } from "react";
import { cn } from "@/lib/utils";

type State = "linear" | "indeterminate" | "circular" | "stepped";

export function Progress({ state = "linear" }: { state?: State }) {
const [v, setV] = useState(45);
useEffect(() => {
  if (state === "indeterminate") return;
  const target = state === "stepped" ? 60 : 65;
  setV(target);
}, [state]);

if (state === "circular") {
  const r = 32;
  const c = 2 * Math.PI * r;
  return (
    <div className="grid w-full max-w-xs place-items-center">
      <div className="relative grid h-24 w-24 place-items-center">
        <svg className="absolute inset-0 -rotate-90" viewBox="0 0 80 80">
          <circle cx="40" cy="40" r={r} stroke="currentColor" strokeWidth="6"
            className="text-muted/40" fill="none" />
          <circle cx="40" cy="40" r={r} stroke="currentColor" strokeWidth="6"
            className="text-lab-accent transition-[stroke-dashoffset] duration-500"
            fill="none" strokeDasharray={c} strokeDashoffset={c * (1 - 0.65)} strokeLinecap="round" />
        </svg>
        <span className="font-mono text-sm">65%</span>
      </div>
    </div>
  );
}

if (state === "stepped") {
  return (
    <div className="w-full max-w-md">
      <div className="mb-2 flex justify-between font-mono text-[11px] text-muted-foreground">
        <span>步驟 3 / 5</span>
        <span>60%</span>
      </div>
      <div className="flex gap-1">
        {Array.from({ length: 5 }).map((_, i) => (
          <div
            key={i}
            className={cn(
              "h-1.5 flex-1 rounded-full transition-colors",
              i < 3 ? "bg-lab-accent" : "bg-muted",
            )}
          />
        ))}
      </div>
    </div>
  );
}

return (
  <div className="w-full max-w-md">
    <div className="mb-2 flex justify-between font-mono text-[11px] text-muted-foreground">
      <span>上傳中⋯</span>
      <span>{state === "indeterminate" ? "—" : `${v}%`}</span>
    </div>
    <div className="relative h-1.5 overflow-hidden rounded-full bg-muted">
      {state === "indeterminate" ? (
        <div className="absolute inset-y-0 left-0 w-1/3 animate-[shimmer_1.4s_ease-in-out_infinite] rounded-full bg-lab-accent" />
      ) : (
        <div className="h-full rounded-full bg-lab-accent transition-all duration-500" style={{ width: `${v}%` }} />
      )}
    </div>
    <style jsx>{`
      @keyframes shimmer {
        0% { transform: translateX(-100%); }
        100% { transform: translateX(300%); }
      }
    `}</style>
  </div>
);
}

何時用

  • ✅ 上傳/下載進度(determinate)
  • ✅ 未知時長的 loading(indeterminate)
  • ✅ 多步驟流程的整體進度

何時別用

  • ❌ 瞬間完成(< 500ms 不用秀)
  • ❌ 進度跳動劇烈(看起來假,反而焦慮)
  • ❌ 錯誤情境(請切到 error state)

我踩過的坑

  1. 進度回跳:30% → 25% 顯得 buggy。只進不退。

  2. indeterminate 動畫太慢:1s 一次 OK,3s 一次像當機。

  3. 到 100% 沒給結束反饋:使用者不知道好了沒。秀 checkmark + 訊息。

相關模式

  • PE05
  • PD06

搜尋

按 ⌘K 隨時開啟