🌐首頁秒開、其餘慢慢來:用「漸進式載入」把大檔影像瀏覽變順暢(含完整範例與最佳化心法)
TL;DR:別一次把 N × M 張頁面全拉完才顯示。改成「先顯示第一頁」,其餘頁面在背景持續抓,每抓完一張就立即渲染。這樣首屏秒開、操作不卡、使用者體驗直線上升。本文用全新、與任何既有專案無關的程式碼示範,並附上錯誤處理、併發控制、快取與中止載入等實務細節。
為什麼要「先渲染一頁」?
大量影像(或頁面)檢視常見痛點:
-
首屏過慢:一次丟出大量請求,使用者在空白畫面乾等。
-
瀏覽遲滯:等全部回來再渲染,主執行緒忙到不行。
-
邊看邊載更符合人性:多數人只想先看第一頁,其他頁慢慢補上即可。
解法:把載入流程「分段化」
-
第一步:只要第一頁 → 立刻顯示
-
第二步:背景依序拉剩下的頁面
-
第三步:每成功下載一頁就立刻渲染(或更新縮圖、頁碼、進度條)
核心概念(白話)
-
輸出資料結構:
pages = [{ index, dataUrl }] -
渲染函式:
renderPage(currentIndex)只負責把目前的頁面貼到 Viewer -
載入策略:
loadFirstThenStream(docId)-
拿第一頁 → 立刻
renderPage(0) -
其餘頁用
for…of或受控並行去抓 -
每抓完一張:
pages.push(newPage)→renderPage(pages.length - 1)
-
範例一:最小可行版本(完全與任何專案無關)
範例端點(假設):GET /api/docs/:docId/manifest→ 回傳{ totalPages: number }GET /api/docs/:docId/page/:pageNo→ 回傳{ ok: true, image: "data:image/png;base64,..." }
這段程式做到了什麼?
-
首屏秒開:第一頁到手就渲染
-
持續增量:後續頁面逐一加入
state.pages -
每抓一張就顯示:
renderPage(state.pages.length - 1) -
可隨時中止:
AbortController避免資源浪費
範例二:加入「受控併發」讓載入更快但不塞爆
同樣與任何現有專案無關,只示範技巧。
思路:一次開 K 條下載任務(例如 4 條),每條完成再補下一頁,直到載完。
優點
-
加快整體完成時間
-
不會一次開上百個請求塞爆瀏覽器/網路
-
照樣維持「抓一張、顯示一張」
進階技巧與實務整理
1) 使用者切換文件時要中止舊任務
-
AbortController是你的好朋友 -
手機網路不穩時,避免僵死請求耗盡資源
2) 記憶體管理
-
影像很多很大?考慮:
-
只保留「目前頁附近」的緩衝(例如 ±5 頁),其他用 Object URL / Cache API 策略
-
改用 PDF viewer(瀏覽器內建 / 第三方)做分頁流式載入
-
3) 失敗重試與退避
-
下載失敗時
setTimeout+ 指數退避(100ms → 200ms → 400ms) -
重試次數(例如最多 3 次)要可設定
4) UX 細節
-
進度提示:
已載入 X / Y、細小 Skeleton UI -
鍵盤操作:← / → 切頁、Home/End 首末頁
-
可及性 (a11y):
aria-live="polite"告知頁面更新
5) 快取
-
網頁端可用
caches.open('doc-cache'),Key 用docId+pageNo -
後端可做 CDN / ETag / Cache-Control
-
文件未變更就直接從快取還原,省流量更快
常見 QA
Q1:為什麼不是全部同時載?
同時開過多請求會拖慢首屏,且容易打滿頻寬、導致卡頓。受控併發或序列下載更穩定。
Q2:可以邊載邊顯示縮圖嗎?
可以。後端提供小尺寸與原尺寸兩個端點,前端先載小圖顯示,再替換大圖(漸進式增量清晰)。
Q3:要不要換成 Service Worker?
如果需要離線瀏覽、背景同步、跨頁共享快取,Service Worker 很值得。但本文解法不依賴 SW,也能拿到 80% 以上的體驗提升。
範例三:極簡版 API(伺服器端,僅示意)
下列僅為示意,你可以用任意後端(Node、Go、Java、.NET…)實作,只要符合前述回應格式即可。
結語
先顯示第一頁、其餘慢慢補,是處理重量級影像/頁面瀏覽最直接有效的 UX/效能升級:
-
首屏時間顯著縮短
-
使用者不再面對空白與卡頓
-
架構彈性佳:可以逐步加入受控併發、快取、重試與中止
把本文的「先首屏、後串流、每張立即渲染」設計套進你的檢視器,你會立刻感受到滑順的差異。必要時再配合後端預先轉檔或快取,整體體驗就能到位。
留言
張貼留言