首发于KFive
复杂情况下canvas实践,性能问题和崩溃问题的定位与解决

复杂情况下canvas实践,性能问题和崩溃问题的定位与解决

作者:爱民,大爽,王群

多数情况下,前端工程师都是使用HTML、CSS和JavaScript通过DOM去实现页面效果,HTML就好像是整个页面的骨架,CSS通过与骨架相关联成为附着在上面起到妆点作用的皮肤,JavaScript就是实现交互和变换的灵魂。这些技能也成为大多数初级工程师必备的技能,相比于一般的多媒体场景,一些特殊场景下可能还会涉及到2D和3D等可视化的Canvas、webGL、SVG等复杂的展现和交互方式的业务实现。

本文主要是通过一个具体的项目案例,通过技术实现具体地说明在canvas中遇到的一些问题,比如各种限制、性能问题和崩溃问题的产生原因是什么?如何定位这些问题?如何解决这些问题?希望通过这篇文章能够帮助您快速了解可视化的一些常见问题,以及如何调试和定位问题

  • 案例
  • 实现需求/解决问题
  • 性能调试方式
  • 可视化工具
  • 总结

1. 案例:

有这样一个H5游戏地图的需求,用来记录当前用户闯关进度过程,每次闯过一关沿着关卡路线通过动画方式移动到下一关,整个地图可能很长一屏仅能显示一部分地图,要求过度自然,热切适配移动端(手机和pad,安卓、ios和鸿蒙操作系统)。

图:H5游戏地图效果

游戏中卡通人物在地图中移动的动画效果,如图所示。

动画效果https://www.zhihu.com/video/1458809637184765953

是不是有了这个动图,我们就基本上搞清楚了整个需求了呢?下面根据这个需求我们说说我们的前端工程师的是怎样通过技术一步步实现的,能够实现的前端技术很多,为了让一些前端常见问题浮出水面,我们以通过Canvas技术实现为前提。

2. 实现需求/解决问题

整个业务需求包括一个用户卡通形象(大笔),一个背景场景(这个背景场景是具有视差层叠效果,用户可以滑动),移动的路径以及每个关卡定点。

图:设计拆分

通过上图的设计拆分可以发现主要包括视差效果、关卡路径、卡通IP的移动,在滑动时和卡通IP移动时触发视差效果,卡通IP验证关卡路径一关一关的移动。

2.1 视差效果

网页滚动过程中,多层次的元素进行不同程度的移动,视觉上形成立体运动效果的网页展示技术。传统的网页的文字、图片、背景都是一起按照相同方向相同速度滚动的,而视差滚动则是在滚动的时候,内容和多层次的背景实现或不同速度,或不同方向的运动。背景图层1和背景图层N在scroll触发移动时调整background-position就可以实现。

2.2 关卡路径

由于需要足够大的画布完成整个关卡路径的绘制和渲染,这就需要用到canvas,但是这张设计图创建了N个canvas容器,这是为什么呢?

单Canvas尺寸过大造成,超出限制就会产生问题,在iOS表现为渲染失败,在安卓表现为崩溃的问题,所以如果画布需要很大的尺寸则需要将尺寸过大的单个canvas拆分成多个canvas渲染。具体各种设备上canvas尺寸限制如下表格:

图:Desktop中canvas尺寸限制
图:Mobile中canvas尺寸限制

拆分成多个canvas画布就可以了吗?我们发现在移动端真机上iOS渲染失败的现象依然存在,这其实是因为Canvas累计使用内存超限导致的,画布内存使用总量超过最大限制(480 MB)。

图:webkit canvas内存限制

解决这个问题可以有两个方案,一个是减少内存占用,而是及时释放内存空间。屏幕有限,毕竟并不是每个canvas都同时要展现出来,只需要将可视区域的canvas绘制就可以,使用完canvas后把宽高清零。

// 释放canvas空间
canvas.height = 0;
canvas.width = 0;
canvas.remove();
canvas.parentNode.removeChilid(canvas);


Canvas绘制模糊问题,canvas 绘图时,会从两个物理像素的中间位置开始绘制并向两边扩散 0.5 个物理像素。当设备像素比为 1 时,一个 1px 的线条实际上占据了两个物理像素(每个像素实际上只占一半),由于不存在 0.5 个像素,所以这两个像素本来不应该被绘制的部分也被绘制了,于是 1 物理像素的线条变成了 2 物理像素,视觉上就造成了模糊。需要注意的是不要忘了 canvas 的绘制是基于上下文状态的,要在绘制之前根据设备像素比dpr先放大绘图区域,否则无效。

设备像素比 = 物理像素 / 设备独立像素


跨域图片绘制不出来:在canvas上绘制图片时需求里需要获取外域图片,并画到canvas画布上,但是这样画布会被“污染”,一旦画布被污染,就无法读取其数据,例如,不能再使用画布的 toBlob(), toDataURL() 或 getImageData() 方法,调用它们会抛出安全错误。

  • 给 img 元素设置 crossOrigin 属性,值为 anonymous
  • 图片服务端设置允许跨域(返回 CORS 头)


2.3 在路上移动

paperjs 是一个二维图形绘制库,github上有12.9k的star,官方网站将其描述成为矢量图形脚本的瑞士军刀。它提供了支持在<canvas>标签的web浏览器上绘制图形和交互操作。 paperjs提供了一个干净的画布/文档对象和许多功能强大的函数给用户去创建矢量图形和贝塞尔曲线。所有这些功能都整齐地包在一个设计良好的,一致的和干净的接口里。 生成这个曲线路径,将会用到Point,Path等这些类。

使用paperjs 绘制这个s行路径,和ps使用钢笔工具绘制这个s型路径的方式一样。

  • 先使用Point类创建三角形的三个点,为了方便计算高 可以创建一个等腰三角形。
  • 使用Path 类 连接三个点形成一个三角形。

在连接两个边的顶点上拽出两个反方向的句柄, 垂直于三角形的高,


var vector = new Point({
      angle: 0,
      length: 300
  });
  var path = new Path();
  path.segments = [
      [[500, 200]],
      [[500 + Math.sqrt((600 * 600) - (300 * 300)), 500], vector.rotate(-90), vector.rotate(90)],
      [[500, 800]],
  ];
  path.fullySelected = true;
  path.strokeColor = '#ff0000';
  path.strokeWidth = 2;

其中,vetor 就是这个点的句柄,length属性为句柄的长度,句柄越长,半圆弧度越大(句柄的长度越长,半圆的弧度就越圆)。

反方向的弧度也是一样的道理,这样s 型的两个半弧形 就画出来了,拼接到一起,就形成了一个s型。

2.4 问题总汇

  • Canvas尺寸过大造成崩溃,需要控制尺寸;
  • Canvas累计使用内存限制,超限将会整体被释放;
  • iOS中的canvas只支持px;
  • 跨域图片绘制不出来,需要配置跨域;
  • Canvas绘制模糊问题,需要考虑设备像素比;

3. 调试方法

在本次项目中我们经历了多次卡顿和崩溃,我们把这次项目中国年所使用的解决问题的方法分享给大家。抛砖引玉期望和大家一起进步。

如果出现性能问题,如卡顿崩溃等问题。我们怎样去定位?俗话说工欲善其事必先利其器,我们最好的帮手自然是浏览器自带的开发工具。用现在流行的术语说,它是我们解决问题的抓手!

相信大家作为开发的老鸟都会避免大部分低级问题。排查canvas的性能问题我们可以从内存和cpu的使用情况入手,不是内存那么可能是cpu使用不当造成的问题。

3.1 内存使用问题排查

前端一般造成卡顿崩溃问题大部分是由于内存原因造成的。所以我们可以先从内存入手去排查问题!

现有的浏览器都有各自强大的开发工具。如firefox的firebug,Opera的dragonfly。

移动端浏览器的实现和桌面还是有很大的区别,做了很多安全和资源限制,开发过程中比较难发现的问题都会在真机环境大批量的暴露出来。移动端浏览器本身不提供开发工具,移动端都提供了remote调试功能,方法不再赘述。

我们以chrome为例,一起看看怎么去排查性能问题。打开Chrome的开发工具的Memory面板进行观察。如果在一段时间内内存使用量不是持续增加,而是并是有升有降在一个合理区间那么可以认为没有内存使用的问题。

红色向上的箭头表示增加内存的大小,绿色向下箭头表示释放掉的内存大小。

图:内存变换

如果内存没有变化那么我们可以,观察cpu使用情况继续排查。

图:开启cpu使用情况

3.2 cpu使用引起的性能问题排查

使用performance monitor工具可以实时查看页面对cpu和memory等资源的使用情况。

图:performance monitor工具

通过上图我们可以看到当前页面对cpu和内存使用都是非常理想的。内存使用在一些WebGL或者Canvas场景下呈现锯齿状也是正常现象。

3.3 定位引起问题的具体代码

如果在这里发现cpu长时间使用过高或者特定情况下打满,那说明我们的程序存在问题,我们可以借助与performance工具继续精准定位。

图:分析JS函数耗时

性能比较差的调用会有一个红色的角标,顺着调用栈一路追踪我们可以找到耗时比较多的函数。下一步就是针对这个进行优化。

顺着调用栈找下去我们就可以找到罪魁祸首!我们一路找下去,这个task耗时75ms,那么这个函数自己执行的时间耗时61ms,那么肯定是这个函数造成了这个task的性能问题。

图:找到性能不佳的原因

点击这个链接我们可以看到具体的代码,虽然是压缩过的但是通过特征判断我们最终还是可以找到问题的原因。

图:定位到具体文件

4. 可视化工具

web前端工程师的插件积累是非常重要的,这样需求来了知道用什么工具来实现。

Arbor.jsArbor是一个免费的、可视化的图形库,基于矢量创建动态的连接图。它为图形组织和屏幕刷新处理提供了一个高效的、力导向的布局算法。
CartoDBCartoDB是一个地图Web Service,并提供非常丰富API,利用它可以轻松创建动态的、可视化的数据驱动地图。
Chroma.js交互式色彩空间资源管理器,允许预览一组线性插值等距的颜色。
CircosCircos是一个Perl语言开发的自由可视化软件,最初主要用于基因组序列相关数据的可视化,目前已应用于多个领域,例如 影视作品中的人物关系分析,物流公司的订单来源和流向分析等,大多数关系型数据都可以尝试用Circos来可视化。
ColorBrewerColorBrewer是专门为帮助用户选择地图和其他图片配色方案而设计的在线工具。
Cubism.jsCubism.js是D3可视化库的一个插件,用于实现时序图。
D3.jsD3是最流行的可视化库之一,它被很多其他的表格插件所使用。它允许绑定任意数据到DOM,然后将数据驱动转换应用到Document中。你可以使用它用一个数组创建基本的HMTL表格,或是利用它的流体过度和交互,用相似的数据创建惊人的SVG条形图。
Dance.js基于Data.js和Underscore.js的一个简单的数据驱动的可视化框架。
Data.jsData.js是一个JavaScript数据表示框架,提供统一的接口和数据域。
DataWranglerDataWrangler是一个交互式的数据清理和转换的可视化工具。
DegrafaDegrafa是一个功能强大的声明式绘图框架,提供丰富的用户接口、数据可视化和映射。
Envision.jsEnvision.js是一个可以快速创建动态、交互式的时间序列的可视化库。
Flare一组软件工具集,用于在ActionScript中创建交互式的可视化数据。
GeoCommonsGeoCommons是一个可视化的数据地图分析工具。
Paper.js一个矢量图形脚本框架。

总结

Canvas的出现扩展了前端开发能力,具有绘制性更强、非dom结构形式用JavaScript进行绘制、动画性能较高等优点,特别适合可视化的场景。但是,在选择使用这项技术实现需求是千万不要盲目乐观,起码在真机上表现出的这些问题需要注意,了解了这些问题和调试方式或许会对你的开发有所帮助。

参考资料

使用paper js 绘制曲线路径

十分钟上手chrome性能分析面板 - 云+社区 - 腾讯云

canvas-size: Determine the maximum size of an HTML canvas element and test support for custom canvas dimensions

iOS/safari canvas memory leak

Canvas 绘图模糊问题解析

怎么在移动端调试web前端?

Chrome 开发者工具的性能选项卡使用教程(译文)

你应该知道的Canvas使用及常见问题 - 掘金

Web工程师必备的43款可视化工具

编辑于 2022-02-22 12:14