加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 资源网站 > 资源 > 正文

小程序Canvas性能优化实战

发布时间:2020-12-14 19:03:28 所属栏目:资源 来源:网络整理
导读:案例背景 需求: 在小程序中使用canvas组件绘制地铁图,地铁图包括地铁线路、站点图标、线及站点名称文字,绘制元素为线、圆、图片、文字。 支持拖动平移和双指缩放。 问题: 小程序中的canvas性能有限,特别在交互的过程中不断触发重绘会引发严重卡顿。 基

案例背景

需求:

  1. 在小程序中使用canvas组件绘制地铁图,地铁图包括地铁线路、站点图标、线及站点名称文字,绘制元素为线、圆、图片、文字。
  2. 支持拖动平移和双指缩放。

问题:

小程序中的canvas性能有限,特别在交互的过程中不断触发重绘会引发严重卡顿。

基本实现

在不考虑优化的情况下,先说说如何实现绘制和交互。

数据格式

首先看看数据,服务返回的数据中每个元素都是独立的,包括该元素的样式及坐标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 线路数据
lineData = { path: [x0,y0,x1,y1,...],strokeColor,strokeWidth }

// 站点数据:分为普通站点和换乘站点
// 普通站点绘制简单圆形
stationData = { x,y,r,fillColor,"Microsoft YaHei"; height: 20px;'>// 换乘站点绘制换乘图标(png图片)
stationData_transfer = { x,width,height }
// 线路名称
lineNameData = { text,x,fillColor }
// 站点名称
stationNameData = { text,y }

绘图API

绘制的时候遍历绘制元素数组,根据元素类型设置上下文样式,绘制及填充。接口参考:https://developers.weixin.qq.com/miniprogram/dev/api/CanvasContext.html。

    交互实现

    实现交互主要步骤如下:

      bindtouchstart、bindtouchmovebindtouchend实现对用户拖动和双指缩放的监听,得到拖动位移向量、缩放比例,触发重绘
    1. 绘制时通过scaletranslate在不用对数据坐标进行处理的情况下实现缩放和平移

    最终得到的结果如下,平均渲染时长为42.82ms,真机(ios)验证:龟速移动,画面延迟非常大。

    优化方法

    完全不了解canvas优化方案的同学可以先看看:?canvas的优化。

    避免不必要的画布状态改变

    参考Canvas 最佳实践(性能篇)?,绘图上下文是一个状态机,状态的改变是有一定开销的。画布状态改变这里主要指strokeStylefillStyle等样式的改变。

    如何减少这部分的开销呢?我们可以尽量让样式相同的元素放在一起进行一次性的绘制。观察一下数据可以发现,很多站点元素样式都是相同的,那么在绘制之前可以先做一次数据的聚合,将样式相同的数据组合成一条数据:

    15
    16
    function mergeStationData(mapStation) {
    let mergedData = {}
    mapStation.forEach(station => {
    let coord = `${station.x},${station.y},${station.r}`
    let stationStyle = `${station.fillColor}|${station.strokeColor}|${station.strokeWidth}`
    if (mergedData[stationStyle]) {
    mergedData[stationStyle].push(coord)
    } else {
    mergedData[stationStyle] = [coord]
    }
    })
    return mergedData
    }

    聚合后,329条站点数据合并为24条,有效的减少了90%的冗余状态改变开销。修改之后测试一下,平均渲染时长降到了20.48ms,真机验证:移动稍快了一些,但画面仍有较高延迟。

    合并数据的时候需要注意,此应用场景下各站点是没有互相压盖的,而如果有压盖顺序的话,在合并时只能合并相邻且样式相同的数据。

    减少绘制物

      17.02ms,真机验证:同上,没有太多变化。

    1. 筛除过小的绘制物:

      ?

      当用户在缩小图像时,文字和站点会由于尺寸太小而看不大清,在不影响用户体验的前提下可以考虑直接去掉。根据测试,最终决定在显示比例小于30%时去除文字和站点,这个级别下的渲染时长从22.12ms,减少到了9.68ms。

    降低重绘频率

    虽然平均渲染时长已经低了很多,但是在交互时却仍有较高的延迟,这是因为每次ontouchmove都会将渲染任务加入到异步队列中,事件触发频率远高于每秒能够执行的渲染次数,导致渲染任务严重积压,不断滞后。在PC端一般使用requestAnimationFrame解决这个问题,小程序里没有,但是可以自己实现,参考微信小程序中使用requestAnimationFrame:

    17
    const requestAnimationFrame = function (callback,lastTime) {
    var lastTime;
    if (typeof lastTime === 'undefined') {
    lastTime = 0
    }
    var currTime = new Date().getTime();
    var timeToCall = Math.max(0,30 - (currTime - lastTime));
    lastTime = currTime + timeToCall;
    var id = setTimeout(function () {
    callback(lastTime);
    },timeToCall);
    return id;
    };
    const cancelAnimationFrame = function (id) {
    clearTimeout(id);
    PC端我们一般将渲染间隔控制在16ms左右,但是在小程序中考虑到性能限制,且移动端各机型性能不一,所以这里留了一些空间,控制在30ms,对应到30FPS左右。

    但如果一直循环调用也会造成静止状态下不必要的开销,所以可以在交互开始ontouchstart和结束ontouchend时分别开启、停止动画:

    animate(lastTime) {
    this.animateId = requestAnimationFrame((t) => {
    this.render()
    this.animate(t)
    },
    stop() {
    cancelAnimationFrame(this.animateId)
    修改之后真机验证一下:画面比较流程,有轻微卡顿,但不会延迟。

    其他注意

      translate要搭配saverestore一起使用;但也可以使用setTransform直接重置矩阵。从理论上看这样应该能节省开销,但实际测试并没什么效果,平均渲染时长在18.12ms。这个问题有待研究。
    1. 小程序中避免使用setData保存与界面渲染无关的数据,以避免引起页面重绘。

    优化结果

    经过以上优化,渲染时长从42降到了17ms左右,真机验证下安卓机型普遍非常流畅,体验很好;ios机型有轻微卡顿,且随着使用时长卡顿逐渐明显,后期可以深入研究下是否有内存管理的问题。

    (编辑:李大同)

    【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读