重點數字
127.20 MB
改 384px 可省的記憶體(無畫質損失)
真正的問題是像素尺寸,不是檔案數量。530 個檔案裡有 405 個是 480 × 480 的遊戲 icon。在 500 px 斷點以下,Lobby 是用 width: 27.2vw 排 icon,所以 iPhone 需要的實體像素是 viewportWidth × 0.272 × DPR。整個 iPhone 系列裡最壞的情況是 359 px(16 Pro Max)。也就是說連最大最密的 iPhone 都被多塞了約三分之一的像素,其他機種更多。改成 384 × 384 可以把遊戲 icon 的解碼記憶體從 353 MB 降到 226 MB,而且畫面看不出差別。
依用途看體積
尺寸分布 — 實際上到底用到幾種尺寸
各 provider 的遊戲 icon — 402 個檔案、21.67 MB
重複下載真正的代價 — 以及哪些不算在它頭上
單次載入中有 93 個 URL 被抓不只一次,換來 95 個多餘請求、4.89 MB 可以省掉的傳輸量 —
佔整頁下載量的 13%。
造成的損失
- 快取是冷的時候,這 4.89 MB 要全額付。第一次來的使用者、快取被清掉的人,
以及每一個從全新 WebView 快取開始的 LINE LIFF session,都是實打實地多吃這些頻寬。
在行動網路上,這就是 31.59 MB 的頁面和 36.48 MB 的頁面的差別。
- 快取是熱的,也還是佔掉一個請求名額。命中快取不是零成本 — 瀏覽器還是要建立
<img>、解析 URL、去查快取,這個請求一樣佔用連線池的名額。
在一開始那波大量請求裡,這 95 個多餘的請求正好在大廳要畫面的當下,跟 600 多個
真正需要的請求搶資源。
- 首次繪製時的隊頭阻塞。前 3 秒的 618 個請求全部打向同一個 host。不管那條連線的
多工上限是多少,其中有 95 個名額是拿去重抓頁面已經有的資料,害使用者真正看得到的
icon 更晚出現。
- CDN 和來源站的帳單多付一筆,付的是完全沒有帶來新東西的請求,而且每一個
session 都要再乘一次。
重複下載不會造成的成本
它們不會讓記憶體翻倍。瀏覽器的解碼 bitmap 快取是用 URL 當 key,所以同一個檔案被要求
三次,還是只解碼一次、只留一份。這也是為什麼上面那個 414 MB 的解碼記憶體,完全是像素尺寸
造成的,跟重複次數無關。解決重複下載是頻寬和延遲的勝利;解決尺寸才是記憶體的
勝利。這是兩個獨立的問題,而且兩個都值得做。
重複是從哪來的
- 遊戲 icon(50 個 URL、2.78 MB)。同一個 icon 出現在不只一條 rail —
推薦/熱門清單和 All 的格狀清單都有 — 而每一份清單都自己用
Provider + Code.toLowerCase() 組出 URL、自己建一個 image。
這些清單串起來的時候沒有做去重,所以熱門遊戲出現在幾份清單裡就被抓幾次。
- 活動 banner(39 個 URL、2.07 MB)。每張 banner 都被抓兩次:一次是
<img>,另一次是同一個元素或它外層的 CSS
background-image。
只要在合併後的預載清單上套一個 Set,再把每張 banner 的
<img> 或 CSS 版本擇一拿掉,基本上就全部解決了。畫面不用改 —
最後呈現在螢幕上的像素完全一樣。
iPhone 上的記憶體成本 — 真正會痛的平台
476 個 <img> 解碼後佔用 414.6 MB 的 RGBA bitmap。圖片解碼後的記憶體是
寬 × 高 × 4 位元組,跟壓縮得多好完全無關 — 所以一張 56 KB、480 × 480 的 WebP
在記憶體裡是 0.88 MB,大約是檔案大小的 16 倍。這個數字在任何裝置上都一樣,
因為它只取決於來源像素,跟螢幕無關。桌機扛得住,iPhone WebView 扛不住。
一張遊戲 icon 在真正的 iPhone 上有多大?
在 500 px 斷點以下,Lobby 用 .gameCard-area .s-card { width: 27.2vw } 排每一張 icon —
是視窗寬度的比例,不是固定的框。所以一張 icon 需要的實體像素是
viewportWidth × 0.272 × devicePixelRatio。所有 iPhone 直立時都在 500 px 以下,
因此全部走這條規則。(≥ 500 px 會變成固定 145 px,桌機是 128 px。)
整個 iPhone 系列最壞的情況是 359 個實體像素(16 Pro Max:440 × 0.272 × 3)。
而每張 icon 都輸出 480 × 480。所以連最大、最高密度的 iPhone 都被多給了大約三分之一
的像素 — 其他機種只會更多。
每個選項的成本與節省
下面的檔案大小是實測,不是估算:隨機抓 60 張 480 × 480 的 icon,用 Lanczos 縮圖後
重新編碼成 WebP。選 quality 80 是因為把原圖以 480/q80 重編後只有現在大小的 94% —
代表來源檔大約落在 q82,所以用 q80 比較是同級比較,而不是偷偷降畫質。
縮小不會糊,只有放大才會。任何像素數達到 359 的方案,在所有 iPhone 上都是視覺無損。
建議:384 × 384。它剛好是 128 px 版位的 3 倍,蓋過 359 px 的最壞情況還有餘裕,
而且在每一支 iPhone 上都不會被放大,所以視覺上完全一樣。實測結果:遊戲 icon 從
21.67 MB 降到 14.82 MB,解碼後記憶體從 353.32 MB 降到 226.12 MB —
在你最在意的平台上省下 127.20 MB,畫面看不出任何差別。
想再壓的話,320 × 320 可以省 196.29 MB,而且只有 Pro Max 會被放大 1.12 倍 —
在 3 倍密度的螢幕上實際看不出來。256 × 256 就太過頭了:所有 DPR 3 的 iPhone 都會放大
1.40 倍,那才是開始看得出糊的地方。本報告較早的版本是以桌機 DPR 1 量測為基準建議 256,
那個基準是錯的,這裡已經更正。
兩全其美的作法:用 srcset 準備 256/384 兩種,DPR 1 和 DPR 2 的裝置拿小的,
DPR 3 的 iPhone 拿大的 — 沒有裝置會多抓,也沒有任何一張會被放大。
最吃記憶體的圖 — 依解碼後的 bitmap 排序,不是檔案大小
其他省記憶體的做法
- 圖片輸出成它實際顯示的尺寸。效益最大的一招 — 見上面的表。用
srcset 準備 128/256 兩種,DPR 1 的裝置就會自動拿小的那張。
- 畫面外的格子不要放進 DOM。可視範圍只顯示大約 36 張,卻解碼了 476 張,
這是問題核心;延遲載入再加上格狀清單的視窗化,瀏覽器就只會同時留一兩個螢幕份量的
bitmap。
- 幫畫面外的格子加上
content-visibility:auto,讓合成器可以把捲出畫面的
格子的點陣資料丟掉,而不是一直留著。
- 格子裡的圖加上
decoding="async",這樣在一開始那波 600 多個請求時,
解碼工作不會卡住主執行緒。
- 把請求清單去重。那 95 個多餘的請求不會增加解碼記憶體(快取會把 bitmap 去重),
但確實多花 4.89 MB 傳輸量,而且在首次繪製時跟其他請求搶同一個連線池。
- 重新檢討 312 × 624 的 jackpot banner。頁面上有四張,每張解碼後 0.78 MB,
而且原始檔名直接就是自己的重量(
_900kb、_500kb、
_300kb)— 很明顯是手動匯出的,不是走 build 流程產生的。
結論
- 480 × 480 的 icon 超出 iPhone 需要的尺寸。有 405 個檔案是 480 × 480,但 iPhone 走
27.2vw 規則,最壞情況(16 Pro Max)只需要 359 px。遊戲 icon 佔了總量 31.59 MB 裡的 21.67 MB,也是解碼記憶體 414.6 MB 裡最大的一塊 — 這頁效益最大的一個施力點。
- 8 個檔案超過 200 KB,合計 4.36 MB。最誇張的是 jackpot/大獎 banner,
檔名直接寫著自己的重量 —
鐵木真_900kb.webp 是 312 × 624、923 KB —
再加上兩條 1920 px 寬的桌機版邊框圖(header-bg-desktop 400 KB、
footer-bg-desktop 403 KB),而它們高度只有 84 px 和 64 px。
- 95 個重複請求花掉 4.89 MB。單次載入中有 93 個 URL 被抓不只一次:
50 個遊戲 icon(同一個 icon 在推薦 rail 和 All 格狀清單各抓一次,而不是重用)
和 39 張活動 banner。
- 活動 banner 出了兩種格式。每張 banner 同時存在
.webp
(由 <img> 載入)和 .jpg/.png
(由 CSS background-image 載入)— 大約 35 張 banner 卻有 70 個不重複檔案。
而且 CSS 那一份,任何以 <img> 為基礎的延遲載入策略都看不到它。
- 全部都在一開始就載完。量到的 626 個請求裡有 618 個(98.7%)在 3 秒內發出。
把整個 11,078 px 高的大廳捲到底,只多觸發了一張圖,而 DOM 裡有 485 個
<img> 標籤,可視範圍卻只顯示大約 36 個。
涵蓋範圍檢查 — 有漏掉什麼嗎?
這份清單是用
PerformanceResourceTiming 建出來的,跟 DevTools Network 面板看到的
是同一組資料,包含快取命中的部分。另外對一次全新載入做了三項交叉檢查,確認沒有東西
從篩選條件底下溜掉:
- 非圖片資源重新檢查過。總共 807 個資源,617 個符合圖片篩選條件、190 個不符合。
那 190 個全部人工看過:62 個 script、78 個 fetch、41 個 XHR、7 個 link、2 個 css。
比較接近邊界的只有一個
.wasm、一個 .aac 音軌、一個
/403check ping,以及三個追蹤用 script — 裡面沒有圖片。
- 完全不走網路的圖片。DOM 裡沒有任何
data: 的
<img>,沒有 <svg>、沒有
<canvas>、沒有 <iframe>、沒有影片 poster,
也沒有 CSS mask-image。唯一內嵌的圖片是 CSS bundle 裡的
6 張 base64 PNG(合計約 21 KB),那算在樣式表裡,不算圖片請求。
- 擴充套件自己的網路紀錄被排除,沒有拿來當資料來源 — 它是滾動式的緩衝區,
頁面穩定下來時已經只剩 30 筆,看不到完整的一次載入。Performance API 的緩衝區則是在
第 26 個資源時就被調高到 50,000 筆,遠早於預設的 250 筆上限開始丟資料的時間點。
每次載入的數字會差幾張,因為促銷輪播和彈窗會換內容;這份清單是其中一次有代表性的載入。
站上全部 530 張圖 — 依分類分組;可以用下面的控制項篩選、排序、調整縮圖大小