如何在網(wǎng)頁上高效渲染 1000 萬張小圖片的?
最近,看到一個名為 10MPage.com 的網(wǎng)站,目標是記錄 2025 年互聯(lián)網(wǎng)的時代印記。每個用戶都可以上傳一張 64x64 像素的小圖片,形成一個龐大的互聯(lián)網(wǎng)影像檔案。
正如名字所暗示的,這個頁面需要承載高達 1000 萬張小圖片。剛開始想到這個概念時,心想如何高效渲染這些圖片?。在本文中,我將分享作者嘗試的各種方案,以及最終實現(xiàn)的高效解決方案。
在你繼續(xù)閱讀之前,可以先訪問一下 10MPage.com 看看能不能猜到我是如何實現(xiàn)的。如果你已經(jīng)打開了10MPage,不妨也為自己上傳一張圖片,搶占一個位置吧!??
HTML <img> 標簽 vs Canvas
首先面臨的選擇是:用傳統(tǒng)的 HTML 元素來渲染,還是用 Canvas 來進行繪制。
方法一:大量單獨的 <img> 標簽
我最初使用了單獨的 <img> 標簽分別加載圖片。我寫了一個腳本,生成一個32x32(共1024張圖片)的圖片網(wǎng)格,用 Laravel Blade 模板進行渲染:
<div class="grid" id="grid">
@for($y = 0; $y < 32; $y++)
<div class="row">
@for($x = 0; $x < 32; $x++)
<div class="tile">
<img src="http://10mpage.test/tiles/{{$y}}x{{$x}}.png" alt="Tile {{$y}}x{{$x}}">
</div>
@endfor
</div>
@endfor
</div>
對應的 CSS 樣式:
body {
margin: 0;
padding: 0;
overflow: auto; /* 允許滾動 */
}
.grid {
display: block;
position: relative;
width: 100%;
}
.row {
display: flex;
}
.tile {
width: 64px;
height: 64px;
box-sizing: border-box;
border: 1px solid #ccc;
}
.tile img {
width: 64px;
height: 64px;
object-fit: cover;
}
這種方式初步看起來不錯,但潛藏幾個嚴重問題:
- 瀏覽器滾動性能差
- DOM 節(jié)點數(shù)量龐大,性能開銷大
- 大量圖片同時加載,網(wǎng)絡請求數(shù)激增
- 難以實現(xiàn)平滑滾動或高級動畫效果
方法二:Canvas 繪制圖片
于是嘗試了 Canvas 方式。首先,通過繪制一個棋盤格圖案來測試 Canvas 渲染效率:
// 簡化的棋盤格Canvas繪制代碼示意:
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const tileSize = 64;
let translateX = 0, translateY = 0, scale = 1;
// 繪制棋盤格
function drawGrid() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(translateX, translateY);
ctx.scale(scale, scale);
const cols = Math.ceil(canvas.width / (tileSize * scale)) + 2;
const rows = Math.ceil(canvas.height / (tileSize * scale)) + 2;
for (let x = 0; x < cols; x++) {
for (let y = 0; y < rows; y++) {
ctx.fillStyle = (x + y) % 2 ? '#fff' : '#000';
ctx.fillRect(x * tileSize, y * tileSize, tileSize, tileSize);
}
}
ctx.restore();
}
drawGrid();
Canvas 方式優(yōu)勢顯著:
- 靈活的滾動和縮放功能
- 極大減少 DOM 節(jié)點
- 性能優(yōu)秀,支持高級動畫和交互效果
經(jīng)過對比后,我最終選擇了 Canvas 方式,它提供了更大的靈活性和更好的渲染效率。
如何優(yōu)化圖片加載效率?
雖然 Canvas 的性能不錯,但加載數(shù)百萬張小圖片仍然存在巨大挑戰(zhàn)。假設以一個標準的 1080p 屏幕為例:
- 寬度:1920px / 64px ≈ 30 張圖片
- 高度:1080px / 64px ≈ 17 張圖片
- 共需渲染 30 × 17 = 510 張圖片。
為了實現(xiàn)流暢滾動,頁面還需提前預加載周圍的圖片。如果將屏幕外 8 個方向的圖片也加載,意味著一次滾動需要加載 4080 張圖片,這幾乎是不可能瞬間加載完畢的。
解決方案:合并小圖片到大圖塊。
將小圖片合并成大圖塊
為解決單獨圖片加載產(chǎn)生的網(wǎng)絡請求過多的問題,設計了一個后端 PHP 控制器,將 16 × 16(256張) 小圖片合并成一個大的圖片塊(每個塊1024×1024像素)。
用戶訪問頁面時,瀏覽器將僅需加載較少數(shù)量的大圖塊,而非大量單獨圖片。這極大地減少了網(wǎng)絡請求次數(shù),提升加載速度。
例如上面的例子,現(xiàn)在只需加載 24 張 大圖塊,而非4080張單獨圖片:
- 寬度:5760px / 1024px ≈ 6 張
- 高度:3240px / 1024px ≈ 4 張
- 6 × 4 = 24 張圖片,負載完全可控!
未上傳圖片的位置顯示為“?”號,清晰表示未填充。
一些提升用戶體驗的小技巧
為了更好地隱藏大圖塊加載細節(jié),提升用戶體驗,作者采用了一些小技巧:
- 加載動畫始終顯示為 64×64 的小塊,使用戶感知不到是加載了更大的圖片塊。
- 網(wǎng)格總是方形加載,避免出現(xiàn)邊界空白的視覺問題。
經(jīng)驗與總結
回顧整個過程,從最初的逐個加載小圖片,到探索 Canvas,再到通過圖片塊合并優(yōu)化加載效率,每一步都是在不斷優(yōu)化用戶體驗與性能之間的平衡。