canvas 正在慢慢吃掉你的内存...

5,211 阅读3分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

canvas 概述

canvas 是一个可以通过 JavaScript 脚本来绘制图形的 HTML 元素,通常我们可以通过 canvas 来绘制图表、制作构图或者简单的动画。

与 canvas 的偶遇

在最近开发工作中,我最经常使用到 canvas 的场景是拿到已知的图片 URL,利用 canvas.toDataURL() 方法,将其转换为 base64 的图片格式。

除此之外,canvas 提供了 toBlob() 方法来创建 Blob 对象,这意味着,我们可以利用 canvas 将图片 URL 转换为 Blob 对象。

由此可见,当需要通过图片 URL 来转换为其他图片形式,我们就得经常和 canvas 打交道。

canvas 内存限制

如果你对 canvas 并不了解,就很容易忽略 canvas 的内存问题。当我看到这篇文章 mp.weixin.qq.com/s/hFG1ypsEc…,我才知道,如果 canvas 创建后没有及时回收,是会占用内存的,占用过多甚至会超出 canvas 内存限制,导致图片加载失败。

举个例子,如下面的代码,我们通过数组收集 canvas 对象,防止它被垃圾回收,并且声明一个创建 canvas 的方法,每个 canvas 宽高都为 512px,一个 canvas 所占用的内存即为 1MB:

html:

<div>
    <span>内存单位: MB</span>
    <input type="number" id="number" />
</div>
<div>
    <button id="create">创建</button>
</div>

JavaScript:

// 创建数组收集canvas,防止被垃圾回收
let canvasQueue = [];

// 创建 1MB canvas
const create1MCanvas = () => {
    const size = 512;
    const canvas = document.createElement("canvas");
    canvas.width = size;
    canvas.height = size;
    const context = canvas.getContext("2d");
    context.fillRect(0, 0, size, size);
    return canvas;
};

// 创建 n 个 1MB 的 canvas
const createNMCanvas = (n) => {
    for (let i = 0; i < n; i++) {
        canvasQueue.push(create1MCanvas());
    }
};

const input = document.querySelector("#number");
const button = document.querySelector("#create");

button.addEventListener("click", (event) => {
    event.preventDefault();
    const number = input.value;
    if (!Number.isNaN(Number(number))) {
        canvasQueue = [];
        createNMCanvas(Number(number));
        console.log(`创建${number}MB canvas成功`);
    }
});

接下来,我们创建 1000 个 canvas,也就是增加 1GB 内存,看看 GPU 的内存占用情况~

2022-09-19-16-42-32.gif

嗯,如你所见,内存占用确实妥妥增加 1GB 的内存,canvas 对内存确实存在肉眼可见的影响,canvas 可存放多少就取决于计算机设备的情况了,只要设备足够 nb,就能吃得下。比如我的笔记本设备是 16GB 的运行内存,当我增加 10GB 的canvas 时,页面就开始出现卡顿,这说明此时的计算机面对 10GB 内存的增加显得有些吃力。

2022-09-19-17-03-20.gif

canvas 清除行动

因为上面的例子中,每次循环都创建了 canvas 变量,并且通过数组收集,使其不被回收。JS 会在创建变量时为其自动分配内存,在不使用的时候自动释放内存,释放过程就是「 垃圾回收机制」。

既然 canvas 没有被回收,那么我们怎么阻止 canvas 带来的内存消耗呢?

首先,我们只创建一个 canvas,每次循环在画布上绘制完图像后,对画布进行清理。并且去除掉对 canvas 的收集,使其被自动回收,因为在实际开发场景中,在图像绘制完成后我们已经拿到了想要的东西,canvas 已经没啥用了,可以抛弃了。

修改代码如下:

let canvasQueue = [];
let canvas = document.createElement("canvas");
const size = 512;
canvas.width = size;
canvas.height = size;
const context = canvas.getContext("2d");

// 创建 n x 1MB canvas
const createNMCanvas = (n) => {
    for (let i = 0; i < n; i++) {
        context.fillRect(0, 0, size, size);

        // 绘制完后,在这里去拿我们想要的数据 如转换后的 base64 编码
        // context.drawImage(image, 0, 0, w, h);
        // const base64 = canvas.toDataURL('image/png')

        // 清除画布
        context.clearRect(0, 0, size, size);
    }
};

此时,我们增加难度,创建 2GB 的 canvas ,我们可以发现对计算机的内存占用几乎毫无波澜,并且页面也不会出现卡顿或者崩溃的现象。

2022-09-19-16-51-09.gif

写在最后

在实际开发中,我们更多是利用 canvas 来对图片做一些转换操作,这对内存并无影响,但如果创建 canvas 数量多的话,一定要注意 canvas 的最大内存限制问题。很多不起眼的代码,对内存却有着肉眼可见的影响,在开发中多注意 CPU、内存的使用情况和性能指标,有利于增加我们代码的健硕性和稳定性。

参考

mp.weixin.qq.com/s/hFG1ypsEc…

developer.mozilla.org/zh-CN/docs/…