最近,看到一个名为 10MPage.com 的网站,目标是记录 2025 年互联网的时代印记。每个用户都可以上传一张 64x64 像素的小图片,形成一个庞大的互联网影像档案。
正如名字所暗示的,这个页面需要承载高达 1000 万张小图片。刚开始想到这个概念时,心想如何高效渲染这些图片?。在本文中,我将分享作者尝试的各种方案,以及最终实现的高效解决方案。
在你继续阅读之前,可以先访问一下 10MPage.com 看看能不能猜到我是如何实现的。如果你已经打开了10MPage,不妨也为自己上传一张图片,抢占一个位置吧!😉
🔍 HTML <img>
标签 vs Canvas
首先面临的选择是:用传统的 HTML 元素来渲染,还是用 Canvas 来进行绘制。
📌 方法一:大量单独的 <img>
标签
我最初使用了单独的 <img>
标签分别加载图片。我写了一个脚本,生成一个32x32(共1024张图片)的图片网格,用 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 节点数量庞大,性能开销大
- 大量图片同时加载,网络请求数激增
- 难以实现平滑滚动或高级动画效果
📌 方法二: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 方式优势显著:
- 灵活的滚动和缩放功能
- 极大减少 DOM 节点
- 性能优秀,支持高级动画和交互效果
经过对比后,我最终选择了 Canvas 方式,它提供了更大的灵活性和更好的渲染效率。
🚀 如何优化图片加载效率?
虽然 Canvas 的性能不错,但加载数百万张小图片仍然存在巨大挑战。假设以一个标准的 1080p 屏幕为例:
- 宽度:1920px / 64px ≈ 30 张图片
- 高度:1080px / 64px ≈ 17 张图片
- 共需渲染 30 × 17 = 510 张图片。
为了实现流畅滚动,页面还需提前预加载周围的图片。如果将屏幕外 8 个方向的图片也加载,意味着一次滚动需要加载 4080 张图片,这几乎是不可能瞬间加载完毕的。
👉 解决方案:合并小图片到大图块
🖼️ 将小图片合并成大图块
为解决单独图片加载产生的网络请求过多的问题,设计了一个后端 PHP 控制器,将 16 × 16(256张) 小图片合并成一个大的图片块(每个块1024×1024像素)。
用户访问页面时,浏览器将仅需加载较少数量的大图块,而非大量单独图片。这极大地减少了网络请求次数,提升加载速度。
例如上面的例子,现在只需加载 24 张 大图块,而非4080张单独图片:
- 宽度:5760px / 1024px ≈ 6 张
- 高度:3240px / 1024px ≈ 4 张
- 6 × 4 = 24 张图片,负载完全可控!
未上传图片的位置显示为“❓”号,清晰表示未填充。
🎩 一些提升用户体验的小技巧
为了更好地隐藏大图块加载细节,提升用户体验,作者采用了一些小技巧:
- 加载动画始终显示为 64×64 的小块,使用户感知不到是加载了更大的图片块。
- 网格总是方形加载,避免出现边界空白的视觉问题。
🌟 经验与总结
回顾整个过程,从最初的逐个加载小图片,到探索 Canvas,再到通过图片块合并优化加载效率,每一步都是在不断优化用户体验与性能之间的平衡。
首发于公众号 大迁世界,欢迎关注。📝 每周一篇实用的前端文章 🛠️ 分享值得关注的开发工具 ❓ 有疑问?我来回答
本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试完整考点、资料以及我的系列文章。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。