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

yumo6662周前 (04-23)技术文章12

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

相关文章

最好用的滚动条美化插件——jQuery.NiceScroll

nicescroll 滚动条插件是一个非常强大的基于 jQuery 的滚动条插件,不需要增加额外的css,几乎全浏览器兼容。ie6+,实现只需要一段代码,侵入性非常小,样式可完全自定义,支持触摸事件,...

20 个让人惊叹的 JavaScript 单行代码技巧,效率瞬间提升

掌握一些简洁有力的单行代码技巧可以大幅提高编码效率,让JavaScript代码更加优雅。分享 20 个实用且令人惊叹的 JavaScript 单行代码,可能为你的工作带来便利。1. 数组去重const...

CSS view():JavaScript 滚动动画的终结

前言CSS view () 方法可能会标志着 JavaScript 在制作滚动动画方面的衰落。如何用 5 行 CSS 代码取代 50 多行繁琐的 JavaScript,彻底改变网页动画每次和 UI/U...

WINCC如何制作滚动播放的文字

首先,我们先看一下文字播放的效果。如视频所示,文字呈跑马灯的形式进来左右来回滚动,那么在wincc中,如果制作这样的效果呢?带着赖工,学会它!第一步:首先在页面合适的位置,新建一个,静态文本,属性见图...

Selenium 滚动页面至元素可见的方法,看完直接可上手了!

今天为大家带来的内容是:Selenium 滚动页面至元素可见的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧!滚动页面  在...

如何实现页面顶部, 自定义滚动进度条样式

关键词:自定义滚动条、自定义顶部滚动条要实现页面顶部的自定义滚动进度条样式,可以按照以下步骤进行:在HTML中添加滚动进度条的容器元素,通常可以使用一个元素作为容器,放在页面顶部的合适位置。<d...