如何在网页上高效渲染 1000 万张小图片的?

开发 前端
从最初的逐个加载小图片,到探索 Canvas,再到通过图片块合并优化加载效率,每一步都是在不断优化用户体验与性能之间的平衡。

最近,看到一个名为 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>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

对应的 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;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.

这种方式初步看起来不错,但潜藏几个严重问题:

  • 浏览器滚动性能差
  • 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();
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.

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,再到通过图片块合并优化加载效率,每一步都是在不断优化用户体验与性能之间的平衡。

责任编辑:姜华 来源: 大迁世界
相关推荐

2017-05-09 09:36:52

Android App高效显示位图

2021-12-20 07:58:59

GitHub源码代码

2015-01-07 09:11:49

恶意IPipset阻止恶意IP

2013-05-22 09:59:10

HTML 5音频

2012-08-27 10:32:12

2018-03-25 08:44:07

iPhonePDF网页

2014-06-26 16:05:53

2014-06-27 14:36:03

iOS演示APP原型

2018-09-17 13:52:39

UCloudUGC

2018-09-17 14:02:28

UCloudGUC

2012-12-10 09:49:28

2020-11-16 08:07:51

浏览器渲染网页

2018-09-17 14:06:46

UCloudUGC

2021-09-13 09:01:02

Vue 技巧 开发工具

2022-07-04 06:11:00

预劫持攻击多因素认证网络攻击

2022-08-03 10:45:04

人工智能网络安全

2009-11-23 19:51:48

ibmdwWeb

2021-11-18 10:37:28

加密挖矿加密货币安全观察

2023-03-07 10:50:42

Linux命令系统
点赞
收藏

51CTO技术栈公众号