適逢雙十連假,本篇文章先由 AI 產出,後續我再人工確認修改
前幾天把遊戲的核心功能做完了,也放進了 3 個彩蛋。接下來的幾天,我想讓它變成「任何人打開網址就能玩」的樣子。在動手之前,今天先花一篇的篇幅,把兩件之後會用到的事情講清楚:
我的 .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,也就是「不需要進度」。要做進度條,就是把這個位置換成一個函式。
GLTFLoader 內部會把 onProgress 交給 FileLoader 去下載檔案。依 r160 版 FileLoader 的原始碼,每讀到一段資料,就會建立一個 ProgressEvent,包含三個欄位:
| 欄位 | 意思 |
|---|---|
loaded |
目前已經下載了多少位元組 |
total |
檔案總共有多少位元組 |
lengthComputable |
能不能算出總大小(true 或 false) |
不是每次都知道檔案總共多大。依 FileLoader 的原始碼,total 是從伺服器回應的 Content-Length 標頭(或 X-File-Size)讀來的:
total 就是檔案大小,lengthComputable 為 true。total 是 0,lengthComputable 為 false。lengthComputable 的說明是:The
ProgressEvent.lengthComputableread-only property is a boolean flag indicating if the resource concerned by theProgressEventhas a length that can be calculated. If not, theProgressEvent.totalproperty has no significant value.
也就是說,當 lengthComputable 是 false,total 沒有意義,不能拿來算百分比。
所以進度條要處理兩種情況:
下面這段是「怎麼把 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,要等部署後實測才知道。
在自己電腦上跑的網址(例如 localhost),在手機上打不開,因為 localhost 指的是「這台裝置自己」,手機上並沒有我的網頁。所以手機測試最簡單的做法,就是把網頁部署到網路上,手機直接開網址。這也是為什麼接下來要先部署,再測手機。
現在我專案的 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:一開始不縮放。我用的 TrackballControls,依 r160 版原始碼:
touchAction 設成 'none',註解寫「disable touch scroll」,也就是在畫面上滑動時,不會觸發網頁本身的捲動。TOUCH_ROTATE),兩根以上對應縮放與平移(TOUCH_ZOOM_PAN)。我判斷「有沒有點到彩蛋」的程式,監聽的是 window 的 click 事件。手機上輕點畫面,會不會被當成 click,並且點到的位置準不準,也是測試要確認的項目。
等部署完成後,我會用手機把下面這些項目逐一確認:
| # | 檢查項目 |
|---|---|
| 1 | 網頁能打開,載入過程中看得到進度 |
| 2 | 載入約 50 MB 的模型,需要多久、會不會當掉 |
| 3 | 畫面比例正常,左上角的計時與彩蛋數字大小適中、沒有被切掉 |
| 4 | 單指拖曳可以旋轉模型 |
| 5 | 雙指可以縮放模型 |
| 6 | 輕點彩蛋,會出現「已找到 N / 3」的提示 |
| 7 | 輕點非彩蛋的地方,沒有反應 |
| 8 | 手機從直向轉成橫向,畫面會自動調整 |
| 9 | 找完 3 個彩蛋,會跳出結果面板,按「重新開始」能重置 |
每一項的結果,都會放在手機測試那天的文章裡。
今天沒有改程式,整理了兩個重點:
GLTFLoader.load 的第三個參數 onProgress,收到的 ProgressEvent 有 loaded、total、lengthComputable,要分成「能算百分比」和「不能算」兩種情況處理。下一步先把專案部署到網路上,讓它有一個任何裝置都打得開的網址。