最近,看到一个名为 10MPage.com 的网站,目标是记录 2025 年互联网的时代印记。每个用户都可以上传一张 64x64 像素的小图片,形成一个庞大的互联网影像档案。
正如名字所暗示的,这个页面需要承载高达 1000 万张小图片。刚开始想到这个概念时,心想如何高效渲染这些图片?。在本文中,我将分享作者尝试的各种方案,以及最终实现的高效解决方案。
在你继续阅读之前,可以先访问一下 10MPage.com 看看能不能猜到我是如何实现的。如果你已经打开了10MPage,不妨也为自己上传一张图片,抢占一个位置吧!😉
HTML <img> 标签 vs Canvas
首先面临的选择是:用传统的 HTML 元素来渲染,还是用 Canvas 来进行绘制。
方法一:大量单独的 <img> 标签
我最初使用了单独的 <img> 标签分别加载图片。我写了一个脚本,生成一个32x32(共1024张图片)的图片网格,用 Laravel Blade 模板进行渲染:
对应的 CSS 样式:
这种方式初步看起来不错,但潜藏几个严重问题:
- 浏览器滚动性能差
- DOM 节点数量庞大,性能开销大
- 大量图片同时加载,网络请求数激增
- 难以实现平滑滚动或高级动画效果
方法二:Canvas 绘制图片
于是尝试了 Canvas 方式。首先,通过绘制一个棋盘格图案来测试 Canvas 渲染效率:
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,再到通过图片块合并优化加载效率,每一步都是在不断优化用户体验与性能之间的平衡。