最近被 Safari “玩”了一把,为了适配它,做了不少的工作。Safari 在Canvas 上确实留了一手,让我们不得不妨,不过正因为有了 Safari 的限制才能够让我的程序更优,占用更少的内存空间。
Safari 在实现 Canvas 时对内存进行了限制,在不同设备上允许使用的内存不同,具体根据你设备 RAM 的大小计算。当你使用 Canvas 时占用的内存超过限制时会提示:
一但超出限制,使用 getContext('2d') 将会返回 null。但我们不知道 Safari 是如何计算 Canvas 占用的内存空间的。
这就不得不够从源码中找答案,翻开 webkit 的源码(如果你不知道如何查看 webkit 的源码,可以看这篇文章,阅读量已有 8000+)
按提示,直接在源码中搜索相关内容:
从源码中可以看到 Canvas 的内存占用空间为 4 * width * height,允许使用的最大内存空间由 maxActivePixelMemory 这个函数计算,这个函数计算规则如下:
可能你会好奇为啥计算画布的内存空间公式 4 * width * height有个 4,代表什么意思?
想一下画布的本质是什么?比如我在画布上写下 SY 时,它的 data 是什么样的?
我创建一块画布,然后写下 S、Y这两个字母,并打印 imageData
图中返回的数组长度为 4096 = 4 * 32 * 32
看下 data 中具体的值,因为 S、Y 两个字符绘制时发生了重叠,你看到的数值不是 RGB 对应的值。
字母在画布中的本质其实是色值,4 表示 RGBA 的具体内容,这就是 4 的来源。
问题来了,有时候不得不创建很多 Canvas,如何才能够避免内存限制,比如实现 2000页的这种文档,每页内容都由 Canvas 绘制:
难道要创建2000个Canvas?
当然不能这样,需要采用一定的优化策略,比如只绘制可视区域的内容,不可见的部分不进行绘制。当页面即将出现时再进行绘制。这就涉及到一个问题,已经渲染后的内容如何让其释放内存空间。
以前想过通过 clearRect 清除画布中的内容,这种想法可笑至极,即使清空了内容,只要画布还在,内存空间就不会被释放。
也可以把 Canvas 这个元素从 DOM 中删除,来释放内存空间。这样会频繁操作 DOM。
还有一种方法,可以设置 CanvasElement 的 width 和 height 为 0,设置这两个值后画布上内的内容会被清空,内存空间也会被释放。
到这里,只要能够监听每页出现即可,可以直接使用 IntersectionObserver 这个 API,它是异步的,不会卡主线程,相比以前监听滚动事件计算可视区域的元素,更优秀,但是不兼容 IE,好在官方提供了 polyfill 来解决这个问题。详细地址:
https://github.com/w3c/IntersectionObserver/blob/main/polyfill/intersection-observer.js
使用 IntersectionObserver 后我们即可在元素出现时设置 Canvas 的宽度,并进行绘制。
详细代码如下所示:
可能有人会担心每次页面出现时渲染会不会很卡,如果绘制的内容比较少,可以忽略不记。比如每页绘制 4000 个文字,感受不到卡顿,当然在绘制过程中可根据实际情况进行优化。
到此,即使存在2000页也不会消耗很大的内存,相比之前内存空间可节约 90%。
说到这里,不得不提 Safari 还有一个限制,就是它的画布大小有限制,不过其它浏览器也会有限制,所以在创建画布的时候,需要留意画布不要太大。从下面的源码中看到,在 iOS 设备上画布会更小。
前几天,我还踩过个坑:留意 drawImage 的坑,Canvas教程大升级
最后再说一件事,前段时间把 Canvas 的教程升级了,内容还在撰写中。效果如下:
大家加油!!!
demo地址:https://github.com/lefex/FE/tree/master/learn-canvas
长按关注
素燕《前端小课》
帮助 10W 人入门并进阶前端
官网:https://lefex.gitee.io/