背景
项目中涉及了大量的canvas绘制的内容,类似于电子画板。而每当绘制了大量内容切换到写的内容再切回来的时候需要渲染大量canvas,平均在45千左右的数据。每条数据又包含了许多要渲染的point数据。所以造成页面切换的卡顿,另外还出现了网页崩溃的现象。
分析
主要有两点:
页面卡顿的原因: 绘制大量canvas数据造成的。 解决方案:使用分片渲染。
网页崩溃的原因:使用canvas时候有数据泄漏,没有及时回收造成的。 解决方案:手动回收泄漏的数据。
解决实现
分片渲染
网上有很多类似的代码,也可以自己写
let i = 0;
const l = arr.length;
const seg = 300;
let isClear = false;
let timer = null;
timer = setInterval(() => {
if (isClear) return;
let chunkEnd = i + seg;
if (chunkEnd >= l) {
chunkEnd = l;
clearInterval(timer);
}
cb(arr.slice(i, chunkEnd), ...args);
i = chunkEnd;
}, 15);
return {
clear() {
isClear = true;
clearInterval(timer);
}
};
}
数据手动回收
借助工具定位泄露的数据。
工具:谷歌浏览器自带的控制台:内存,性能栏目以及性能监视器。
通过性能分析器可以发现js堆只增不减,
观察内存快照可以发现内存增量超多,并没被回收,可以查看是哪些数据导致增量,并手动进行回收
比如发现这里增量排行高的中有这个pointlistlocal数据,就去代码中找到哪里引用了,并在合适的时机进行清除。
canvas性能优化的扩展
- 离屏渲染
- 避免浮点数坐标,用整数
- 不要再用draw时候缩放图像
- 分层画布渲染
- 能使用css就别使用canvas
- 关闭透明度
- 清除不使用的数据
参考文章:developer.mozilla.org/zh-CN/docs/…
内存泄漏问题拓展
这个需要根据具体泄漏的内容具体分析。去分析有些数据和方法是否有必要使用和保留。
比如通过观察我们的监听器也占用了大量的增量,针对addEventListener进行了优化。
const els = document.getElementsByTagName('*');
// 例一
for (let i = 0; i < els.length; i++){
els[i].addEventListener("click", (e) => {/* 处理点击事件 */}, false);
}
// 例二
function processEvent(e){
/* 处理同样的点击事件 */
}
for (let i = 0 ; i < els.length; i++){
els[i].addEventListener("click", processEvent, false);
}
在上面的第一个例子中,一个新的(匿名)函数在每次循环中被创建一次。在第二个例子中,与之前的匿名函数功能相同的函数被用作事件监听器,但后者所带来的内存开销要更小一点,因为函数只被声明过一次。此外,在第一个例子中,我们不能调用 removeEventListener()
,因为我们没有保留任何对匿名函数的引用(在例子的情况中,是没有保存对循环中创建的多个匿名函数的引用)。而在第二个例子中,processEvent
是一个可被引用的函数,因此可以调用 myElement.removeEventListener("click", processEvent, false)
。
实际上,真正影响内存的并不是没有保持函数引用,而是没有保持静态的函数引用
另外监听滚动事件优化可以 使用 passive 改善滚屏性能