iT邦幫忙

2026 iThome 鐵人賽

DAY 25
0
佛心分享-IT 人自學之術

從空拍到3D展示系列 第 25 篇

Day 25. 進度條原理與手機測試準備

  • 分享至 

  • xImage
  •  

適逢雙十連假,本篇文章先由 AI 產出,後續我再人工確認修改

前言

前幾天把遊戲的核心功能做完了,也放進了 3 個彩蛋。接下來的幾天,我想讓它變成「任何人打開網址就能玩」的樣子。在動手之前,今天先花一篇的篇幅,把兩件之後會用到的事情講清楚:

  1. 載入進度條:模型檔案有 50 MB 左右,使用者打開網頁後會等一段時間,要怎麼讓他知道「正在載入,沒有當掉」?
  2. 手機測試:玩的人不一定用電腦,手機打開時要先確認哪些事?
    今天的內容都是我先讀原始碼與官方文件整理出來的知識,沒有實測結果。實測會放在之後的文章。

一、為什麼需要載入進度條

我的 .glb 檔案大約 50 MB(第一版是 52,404,176 位元組)。網頁一打開,瀏覽器就要先把這個檔案整個下載下來,才能顯示模型。下載的這段時間,畫面上什麼都沒有。

使用者看到的是一片空白,不知道是在載入,還是壞掉了。進度條要解決的就是這件事:讓使用者知道下載進行到哪裡。


二、載入進度條的原理

現在的程式沒有用到進度

目前載入模型的程式是這樣:

loader.load(
  'blender_output.glb',
  (gltf) => { /* 載入完成後做的事 */ },
  undefined,                                   // ← 第三個參數,這裡沒有用
  (error) => console.error('載入失敗', error)
);

GLTFLoader 的 load 一共接受四個參數。依 r160 版的原始碼,它的簽名是 load( url, onLoad, onProgress, onError ):

參數 意思
url 檔案的網址或路徑
onLoad 載入完成時呼叫
onProgress 下載過程中,反覆呼叫
onError 載入失敗時呼叫

我現在在第三個位置放的是 undefined,也就是「不需要進度」。要做進度條,就是把這個位置換成一個函式。

onProgress 收到的是什麼

GLTFLoader 內部會把 onProgress 交給 FileLoader 去下載檔案。依 r160 版 FileLoader 的原始碼,每讀到一段資料,就會建立一個 ProgressEvent,包含三個欄位:

欄位 意思
loaded 目前已經下載了多少位元組
total 檔案總共有多少位元組
lengthComputable 能不能算出總大小(true 或 false)

為什麼有 lengthComputable 這個欄位

不是每次都知道檔案總共多大。依 FileLoader 的原始碼,total 是從伺服器回應的 Content-Length 標頭(或 X-File-Size)讀來的:

  • 讀得到:total 就是檔案大小,lengthComputable 為 true。
  • 讀不到:total 是 0,lengthComputable 為 false。
    MDN 對 lengthComputable 的說明是:

The ProgressEvent.lengthComputable read-only property is a boolean flag indicating if the resource concerned by the ProgressEvent has a length that can be calculated. If not, the ProgressEvent.total property has no significant value.

也就是說,當 lengthComputable 是 false,total 沒有意義,不能拿來算百分比。

所以進度條要處理兩種情況:

  • 算得出總大小:顯示百分比,例如「載入中… 50%」。
  • 算不出總大小:沒辦法顯示百分比,只能顯示「已下載 10.0 MB」這種數字,或一個不確定長度的動畫。

一個示意寫法

下面這段是「怎麼把 event 轉成顯示文字」的邏輯,只是示意,之後實作時會接到畫面上:

function formatProgress(event) {
  if (event.lengthComputable) {                                  // 算得出總大小
    const percent = Math.floor((event.loaded / event.total) * 100);
    return `載入中… ${percent}%`;
  }
  const mb = (event.loaded / (1024 * 1024)).toFixed(1);          // 算不出:只顯示已下載量
  return `載入中… 已下載 ${mb} MB`;
}
  • Math.floor(...):無條件捨去,讓 99.9% 顯示成 99%,不會在還沒完成時就顯示 100%。
  • 1024 * 1024:1 MB 等於 1,048,576 位元組,用來把位元組換算成 MB。
  • .toFixed(1):保留一位小數。
    我用模擬的事件測試了這段邏輯:
模擬事件 顯示結果
算得出總大小,已下載 0 載入中… 0%
算得出總大小,已下載一半 載入中… 50%
算得出總大小,已下載完 載入中… 100%
算不出總大小,已下載 10 MB 載入中… 已下載 10.0 MB

四個都符合預期。但這只是在 Node 裡用假資料測的,真正的網頁上會不會拿到 Content-Length,要等部署後實測才知道。


三、手機測試要先準備什麼

1. 手機要連得到網頁

在自己電腦上跑的網址(例如 localhost),在手機上打不開,因為 localhost 指的是「這台裝置自己」,手機上並沒有我的網頁。所以手機測試最簡單的做法,就是把網頁部署到網路上,手機直接開網址。這也是為什麼接下來要先部署,再測手機。

2. 網頁要告訴手機怎麼排版

現在我專案的 index.html(截至 10/6 的版本),<head> 裡有 charset、標題和樣式表,沒有 viewport 這一行。

MDN 對 viewport meta 標籤的說明是:

The viewport value for the name attribute of a <meta> element gives hints about how the viewport should be sized.

MDN 提到,窄螢幕的手機有時會用比螢幕更寬的「虛擬視窗」來排版,再縮小顯示,viewport 標籤就是用來緩解這個問題。MDN 給的寫法是:

<meta name="viewport" content="width=device-width, initial-scale=1.0" />
  • width=device-width:視窗寬度設成裝置螢幕的寬度。
  • initial-scale=1.0:一開始不縮放。
    沒有這行時,我的遊戲畫面在手機上會是什麼樣子,我還沒有看過,會在測試時確認。

3. 觸控操作:函式庫有支援,但實際手感要測

我用的 TrackballControls,依 r160 版原始碼:

  • 會把畫面元素的 touchAction 設成 'none',註解寫「disable touch scroll」,也就是在畫面上滑動時,不會觸發網頁本身的捲動。
  • 用「指標事件(pointer events)」處理觸控,一根手指對應旋轉(TOUCH_ROTATE),兩根以上對應縮放與平移(TOUCH_ZOOM_PAN)。
    所以以函式庫的設計來看,手機上單指拖曳能旋轉、雙指能縮放。但「設計上有支援」和「實際玩起來順不順」是兩回事,要用手機實測。

4. 點擊彩蛋:寫的是 click 事件

我判斷「有沒有點到彩蛋」的程式,監聽的是 window 的 click 事件。手機上輕點畫面,會不會被當成 click,並且點到的位置準不準,也是測試要確認的項目。


四、手機測試清單

等部署完成後,我會用手機把下面這些項目逐一確認:

# 檢查項目
1 網頁能打開,載入過程中看得到進度
2 載入約 50 MB 的模型,需要多久、會不會當掉
3 畫面比例正常,左上角的計時與彩蛋數字大小適中、沒有被切掉
4 單指拖曳可以旋轉模型
5 雙指可以縮放模型
6 輕點彩蛋,會出現「已找到 N / 3」的提示
7 輕點非彩蛋的地方,沒有反應
8 手機從直向轉成橫向,畫面會自動調整
9 找完 3 個彩蛋,會跳出結果面板,按「重新開始」能重置

每一項的結果,都會放在手機測試那天的文章裡。


小結

今天沒有改程式,整理了兩個重點:

  • 進度條:靠 GLTFLoader.load 的第三個參數 onProgress,收到的 ProgressEvent 有 loaded、total、lengthComputable,要分成「能算百分比」和「不能算」兩種情況處理。
  • 手機測試:先有網址、再檢查 viewport、再逐項測觸控與點擊。

接下來

下一步先把專案部署到網路上,讓它有一個任何裝置都打得開的網址。


上一篇
Day 24. 放入三個彩蛋,在 Three.js 找到它們
系列文
從空拍到3D展示 共 25 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言