iT邦幫忙

2026 iThome 鐵人賽

DAY 18
0
Vibe Coding

看得懂、叫得出、驗得過:30 天 VibeCoding UI 元件實戰系列 第 23

Day 23|Loading 不能只放一顆圈圈:五種等待狀態怎麼選

  • 分享至 

  • xImage
  •  

安安~我是ChiYu~

通知中心留下了一筆新工作:

匯入 24 位成員。

這不是一瞬間完成的動作,我最早給 AI 的 Loading 需求卻只有一句:

資料載入時先放一個轉圈圈。

第一版真的很聽話。按下開始,畫面中央出現一顆 Spinner,然後就沒有然後了。

LumenDesk 匯入 24 位成員時只顯示轉圈圈,沒有工作名稱、完成數量與離開影響

圖 1:系統知道要匯入 24 位成員,畫面卻只剩「資料載入中」;三個問號分別是處理對象、完成數量與現在能不能離開。

管理員不知道正在處理哪一批資料、完成了幾位,也不知道現在關掉頁面會不會讓前面的等待全部歸零。圈圈確實一直在轉,能提供的情報大概只有「它還會轉」。

但這次總數明明是 24 位。只放 Spinner,等於把系統已經知道的進度一起藏起來。

所以今天不把 Loading 當成一種動畫,而是拆成五種責任:SpinnerProgress BarSkeletonLoading OverlayIndeterminate Progress

它們都在告訴使用者「系統還在處理」,回答的問題卻不同:

  • 正在處理哪一件事?
  • 系統知不知道完成比例?
  • 哪一區暫時不能操作?
  • 使用者現在能不能離開?
  • 完成、失敗或取消後,畫面要變成什麼?

我把五種狀態整理在 Loading 元件比較頁

Vibe UI Atlas 的 Loading 元件比較:先判斷能否計算進度、是否已有版型,以及哪一區被暫停

圖 2:Spinner 回答「有沒有在處理」,Progress Bar 顯示完成度,Skeleton 預告版型,Overlay 與不定進度則處理局部暫停和未知比例。

這張圖先問資料和範圍,再選動畫。若 Prompt 只寫「加一個 Loading」,AI 很難知道後端有沒有真實完成量,也很容易把全頁遮罩當成萬用答案。

Progress Bar 第一輪:畫面完成了,可存取值卻回到 0%

2026 年 7 月 24 日,我在 Progress Bar Demo 按下「開始匯入」。

進行中時,工作名稱、百分比與進度列一起更新,按鈕也暫時不能再次啟動。這一段看起來沒有問題。

直到匯入完成。

畫面文字顯示「24 位成員已匯入;進度列已停止」,瀏覽器的可存取樹卻把 progressbar 值呈現成 0%。視覺上已經抵達終點,語意上又被送回起跑線。

階段 畫面與語意結果 當時判斷
尚未開始 0%、準備匯入 24 位成員 合理
進行中 數字、長條與工作文字同步前進 通過
完成畫面 顯示 24 位成員已匯入 視覺結果通過
完成後的 progressbar 可存取值回到 0% 不通過,需要修正或移除已完成元件

如果我只截一張進行中的漂亮畫面,這個 bug 不會出現。Loading 驗收一定要走到工作結束,因為元件最容易把「正在動」做好,卻忘了收乾淨完成、失敗與重試。

修正複測:完成文字和數值都停在 100%

修正後,我在同一天重新跑過完整流程。

進行中的文字、畫面百分比與原生 progress 值同步增加;完成時顯示「24 位成員已匯入」,可見百分比和進度值都維持 100%,沒有再回到 0%。

因此要分清楚:0% 是第一輪曾經發生、目前已排除的問題。 2026 年 7 月 24 日的本機複測已確認完成文字和 DOM 數值一致。

目前仍未完成的是用螢幕閱讀器實際聽完整段動態更新。畫面、DOM 值和完成訊息一致,可以證明修正有效;還不能代替正式輔助科技驗證。

這次經驗也改變了我寫 Loading Prompt 的方式。除了要顯示什麼,我還會寫工作完成後進度元件應停在哪裡、被移除,還是換成結果訊息。

等待二十秒時,畫面至少要回答一個有用問題

把等待拉長到二十秒,幾種元件提供的情報差很多。

Spinner 只能告訴人「系統還在動」;72% 的 Progress Bar 能讓人估計是否繼續等待;排程頁的 Skeleton 則先讓使用者知道,等等會出現標題、時間和資料列。

若只暫停一張權限設定卡,Loading Overlay 應留在那張卡,不要連旁邊說明一起封鎖。外部目錄同步若無法估算完成度,就明確使用 Indeterminate Progress,別捏一個看似很努力、其實和後端毫無關係的 90%。

等待情境 系統知道什麼 使用者最需要知道什麼 合適元件
重新整理一張卡片,幾秒內可完成 正在處理,沒有可靠剩餘時間 這張卡仍在更新 Spinner
匯入 100 位成員,已完成 72 位 總量與完成量 還有多少、是否持續前進 Progress Bar
第一次載入已有版型的排程頁 最終版面大致長相 稍後哪些內容會出現在這裡 Skeleton
只重算權限設定區 哪一區暫時不能操作 其他內容仍可閱讀 Loading Overlay
外部目錄同步,完成時間無法估算 仍在處理,沒有可用比例 系統正在同步什麼 Indeterminate Progress

百分比不是拿來安撫使用者的裝飾。沒有真實完成量,就把比例留白,改用清楚的處理文字。

Spinner:處理短暫局部更新,不能只剩一顆會轉的圖

LumenDesk 的「重新整理成員」只會更新卡片裡的成員清單。等待時間短,也沒有可靠比例,Spinner 很適合。

按下按鈕後,Spinner 出現在被更新的區域,狀態文字同時顯示「正在更新成員清單」。

Spinner 實際 Demo

圖 3:Spinner 只出現在成員卡片內,旁邊的狀態文字交代正在更新哪一份資料。

Spinner 適用的前提是等待短、範圍小,而且使用者不需要知道剩餘時間。若成員清單十幾秒還沒回來,只有旋轉圖案就不夠;至少要說明正在取得什麼、能不能繼續其他工作,以及失敗後怎麼處理。

動畫也不能成為唯一狀態。使用者偏好減少動態效果時,可以降低或取消旋轉,但「正在更新成員清單」仍要保留。等待資訊不能跟著動畫一起消失。

Progress Bar:有真實完成量,才配得上百分比

「匯入 24 位成員」知道總數,也知道目前完成幾位。這時使用者要看的就是進度,不只是一句「還在處理」。

Progress Bar 實際 Demo

圖 4:Progress Bar 從 0% 更新到 100%,並同步顯示已匯入人數;完成後進度值保留在 100%。

原生 <progress>value 時,可以表達可計算的完成度;沒有 value 時,才是不定進度。元件還需要可讀標籤,不能只讓人看到一條長度正在改變的色塊。若它描述某一區內容的更新,處理期間也要讓那個區域呈現忙碌狀態,完成後再解除。MDN:<progress>

進度值還要符合真實工作的特性:

  • 不應在沒有重新開始工作的情況下突然從 72% 倒退到 31%。
  • 批次總量若因驗證失敗而改變,要同步更新分母與說明。
  • 完成 24/24 後,狀態不能又被初始化成 0/24。
  • 若後端只回報階段,不回報筆數,就顯示「驗證檔案」「建立帳號」等階段,不要捏造比例。

假 90% 不會讓工作更快,只會讓使用者多等一段充滿懷疑的時間。

Skeleton:預告已知版型,不要把整頁刷成灰色斑馬

第一次打開排程頁時,最終版型已經知道,資料還沒回來,這時適合用 Skeleton 預留標題、時間和資料列。

Skeleton 實際 Demo

圖 5:Skeleton 對應排程頁真正會出現的標題、時間與資料列,資料完成後由真實內容取代。

Skeleton 的工作是穩定版面和預告結構,不是隨手畫幾條灰線。Carbon 的 Loading Pattern建議它用在已有外形的容器與資料內容,並只作短暫過場;按鈕、輸入欄這類可操作控制項也不該變成假的灰色骨架。

骨架形狀要接近最後內容,否則資料回來時仍會大幅跳動。已經有舊資料的重新整理,也不一定要把內容全部換成 Skeleton;保留舊內容、標示正在更新,通常比讓整頁重新變灰更能維持脈絡。

畫面上的骨架不該被輔助科技讀成一堆還不能使用的假標題、假按鈕。真正需要宣布的是「排程正在載入」與載入完成後的內容更新,不是讓灰色方塊排隊自我介紹。

Loading Overlay:只暫停正在處理的區域

LumenDesk 的權限設定卡在重新計算時,只有那張卡不能操作;右側權限說明仍然可以閱讀。因此 Overlay 只蓋住設定卡。

Loading Overlay 實際 Demo

圖 6:Loading Overlay 鎖住正在重算的權限設定卡,旁邊說明內容維持可讀。

全頁遮罩會把「哪一區正在處理」一起蓋掉,還會讓人以為整個系統都不能使用。除非任何操作都不安全,否則等待範圍應收在真正受影響的區塊。

設定卡裡的控制項在處理期間不能重複操作,鍵盤也不能鑽進遮罩底下;卡片外的說明仍可閱讀。Overlay 不是半透明貼紙,它代表哪一塊介面現在不接受操作。

如果處理失敗,Overlay 必須退場,原本控制項重新可用,並在同一區顯示原因與重試。最糟的狀況不是請求失敗,而是遮罩永遠不消失,使用者連重試都沒有入口。

Indeterminate Progress:誠實承認目前算不出百分比

外部目錄同步可能已經開始,但系統不知道總筆數,也無法估算每筆會花多久。這種情況就直接說「正在同步」,不要先顯示 0%,再讓數字停在那裡陪使用者互看。

Indeterminate Progress 實際 Demo

圖 7:不定進度列顯示系統仍在同步,文字同時說明完成時間目前無法預估。

原生 <progress> 沒有 value 時,就是不定進度;MDN 的 <progress> 文件也區分了有數值和不定進度兩種狀態。

不提供百分比不代表可以只剩動畫。畫面仍要有可讀文字,例如「正在同步權限資料,完成時間尚未可預估」。若系統後來取得可靠總量,也可以從不定進度切換成 Progress Bar;切換時要保留同一個工作名稱,避免看起來像突然開始另一份任務。

使用者能不能離開?Loading 還要交代工作生命週期

同一個 Loading 元件,可能對應兩種完全不同的工作:

  • 頁面綁定工作: 離開頁面就會取消,例如只重整目前卡片。
  • 背景工作: 離開後仍持續,例如匯入大量成員或產生報表。

這件事若不寫,使用者只好靠運氣決定要不要關頁。

背景工作應提供可回看的入口,例如通知中心、工作佇列或專屬狀態頁。回來後要讀到同一個工作 ID 與目前進度,而不是重新啟動第二次匯入。頁面綁定工作若離開就會取消,也應在必要時明確提醒。

取消按鈕同樣不是裝飾。按下後要說清楚:

  • 已完成的 12 位成員會保留、回滾,還是標記為部分完成?
  • 取消請求只是「已送出」,還是後端真的停止?
  • 再次開始時會從頭重跑,還是接續未完成項目?
  • 重試是否使用同一個工作 ID,避免重複建立資料?

長時間工作的難點往往不在 Progress Bar,而在中斷、重試和重複執行。畫面只寫「取消」兩個字,後端卻繼續跑完,使用者收到的就不是回饋,而是一場驚喜。

Loading 選型地圖:使用者到底在等什麼?

Loading 元件選擇圖

圖 8:已知完成度用 Progress Bar;已知版型用 Skeleton;短暫局部工作用 Spinner,其他情境再判斷暫停範圍與進度是否可估算。

這張圖的用途不是找一個最漂亮的 Loading,而是避免把未知時間偽裝成百分比,也避免一張卡片更新時順手鎖住整個頁面。

改寫 Prompt:把進度來源、等待範圍和完成狀態寫清楚

請為 LumenDesk 後台加入五種載入狀態,並明確交代工作生命週期。

- 重新整理成員清單時,只在成員卡片內顯示 Spinner 與「正在更新成員清單」;不要遮住全頁,也不要移走鍵盤焦點。失敗時移除 Spinner,恢復控制項並提供局部重試。
- 匯入已知總數的成員時,用 Progress Bar 顯示已完成筆數、總筆數與百分比;百分比必須來自真實進度,完成後維持 100% 或改成完成訊息,不可重設為 0%。
- 初次載入排程時,用 Skeleton 預留標題、時間與三列資料的位置;資料完成後替換為真實內容,不要把按鈕做成 Skeleton。重新整理已有資料時,優先保留舊內容並標示更新中。
- 重算權限時,只用 Loading Overlay 暫停權限設定卡,其他說明內容維持可閱讀。處理失敗後 Overlay 必須退場。
- 同步外部目錄且無法估算完成度時,使用 Indeterminate Progress 與明確處理文字,不要顯示假百分比;取得可靠總量後才切換為 Progress Bar。
- 匯入屬於背景工作。離開頁面後仍持續,並可從通知中心用同一個工作 ID 回到進度。重試不得重複建立已完成資料;取消時要說明已完成項目的處理政策。
- 360px 手機版不得出現水平捲動;使用者偏好減少動態效果時,可減少動畫但不能移除狀態文字。

動畫長相不是最難的部分。真正不能交給 AI 猜的是:資料能不能計算、哪一區暫停、使用者能不能離開,以及完成、失敗、取消後資料會留下什麼。

驗收 Loading,要一路等到完成、失敗與取消

交付前,我會逐條操作:

  • 短暫、局部且無法估算比例的更新,才使用 Spinner。
  • 有可靠完成度的工作,Progress Bar 顯示真實目前值與最大值。
  • 有既定版型的初次載入,Skeleton 對應真正會出現的欄位與資料列。
  • Loading Overlay 只鎖住正在處理的區域,失敗後能退場。
  • 無法估算完成度時,不定進度列有處理說明,但沒有假百分比。
  • 完成、失敗、重試與取消後,畫面文字和可存取語意是否一致?
  • 離開頁面再回來,背景工作是否接回同一筆,而不是重新啟動?
  • 快速連按開始或重試,是否會建立重複工作?

不要在 Spinner 開始旋轉時就宣布驗收完成;至少陪它走到工作有結果。

進度停在 100%,Loading 消失後又留下五種空白

這輪先抓到 progressbar 完成後回到 0% 的矛盾,再修正並複測到 100%。目前畫面、DOM 值與完成文字一致;尚未完成的只有螢幕閱讀器動態實聽,不能把這一項假裝成已通過。

匯入結束後,Loading 也該退場。但清單接著可能出現資料、找不到結果、請求失敗、離線或權限不足。這些畫面都沒有正常資料,原因和下一步卻完全不同。

我把需求縮成一句:

沒有資料時顯示「目前沒有資料」,並提供重新載入。

LumenDesk 專案頁將篩選零結果、API 失敗、任務完成、離線與權限不足都顯示成目前沒有資料

圖 9:畫面只留下「目前沒有資料」與「重新載入」,五種原因因此共用同一個出口。

這顆「重新載入」看起來很熱心,實際上只擅長重送請求。它不能清除篩選、恢復網路、取得權限,也不能替已完成的工作補上下一步。

明天就從 Loading 消失後的這片空白開始,拆開 Empty、Error、Success、Offline 與 Permission State。這次先查清楚原因,再決定畫面該說什麼、按鈕又能真的解決什麼。

參考資料


上一篇
Day 22|訊息可以自己消失嗎?Toast、Snackbar 與 Notification
系列文
看得懂、叫得出、驗得過:30 天 VibeCoding UI 元件實戰23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言