以下内容转载自totoro的文章《小程序Canvas性能优化实战!》

作者:totoro

链接:https://blog.totoroxiao.com/canvas-perf-mini/

来源:https://blog.totoroxiao.com/

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

腾讯位置服务基于微信提供的小程序插件能力,专注于(围绕)地图功能,打造一系列小程序插件,可以帮助开发者简单、快速的构建小程序,是您实现地图功能的最佳伙伴。目前微信小程序插件提供路线规划、地铁图、地图选点等服务,欢迎大家体验!

我们将陆续推出更多功能的插件,敬请期待!

案例背景

需求:

在小程序中使用canvas组件绘制地铁图,地铁图包括地铁线路、站点图标、线及站点名称文字,绘制元素为线、圆、图片、文字。

支持拖动平移和双指缩放。

问题:

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

基本实现

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

数据格式

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

// 线路数据
lineData = { path: [x0, y0, x1, y1, ...], strokeColor, strokeWidth } // 站点数据:分为普通站点和换乘站点
// 普通站点绘制简单圆形
stationData = { x, y, r, fillColor, strokeColor, strokeWidth }
// 换乘站点绘制换乘图标(png图片)
stationData_transfer = { x, y, width, height } // 线路名称
lineNameData = { text, x, y, fillColor } // 站点名称
stationNameData = { text, x, y }

绘图API

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

• 设置样式:setStrokeStyle, setFillStyle, setLineWidth, setFontSize

• 绘制路线:moveTo, lineTo, stroke

• 绘制站点:moveTo, arc, stroke, fill

• 绘制图片:drawImage

• 绘制文字:fillText

交互实现

实现交互主要步骤如下:

• 通过bindtouchstart、bindtouchmove、bindtouchend实现对用户拖动和双指缩放的监听,得到拖动位移向量、缩放比例,触发重绘。

• 绘制时通过scale和translate在不用对数据坐标进行处理的情况下实现缩放和平移

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

优化方法

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

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

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

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

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,真机验证:同上,没有太多变化。

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

降低重绘频率

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

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)
}, lastTime)
}, stop() {
cancelAnimationFrame(this.animateId)
}

修改之后真机验证一下:画面比较流程,有轻微卡顿,但不会延迟。

其他注意

由于本例中缩放和平移状态是以绝对状态保存的,所以scale和translate要搭配save和restore一起使用;但也可以使用setTransform直接重置矩阵。从理论上看这样应该能节省开销,但实际测试并没什么效果,平均渲染时长在18.12ms。这个问题有待研究。

小程序中避免使用setData保存与界面渲染无关的数据,以避免引起页面重绘。

优化结果

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

小程序Canvas性能优化实战的更多相关文章

  1. 微信小程序之性能优化

    如果做前端仅仅停留在编码和实现业务功能上面,可能进步速度会有些慢,但是如果经历了对页面的性能优化之后而且有所成绩的话那就不同了,因为你对他背后的机制进行了研究,才能做好性能优化. 做微信小程序也是一样 ...

  2. 优化版小程序canvas,增加失败逻辑,及完善文字

    wxml <view class="shareBox" style="backgound:{{isShow ? '#000' : '#fff'}}" wx ...

  3. 网站性能优化实战——从12.67s到1.06s的故事

    文章摘自https://juejin.im/post/5b0b7d74518825158e173a0c 作为互联网项目,最重要的便是用户体验.在举国“互联网+”的热潮中,用户至上也已经被大多数企业所接 ...

  4. Golang 性能优化实战

    小结: 1. 性能查看工具 pprof,trace 及压测工具 wrk 或其他压测工具的使用要比较了解. 代码逻辑层面的走读非常重要,要尽量避免无效逻辑. 对于 golang 自身库存在缺陷的,可以寻 ...

  5. 我把阿里、腾讯、字节跳动、美团等Android性能优化实战整合成了一个PDF文档

    安卓开发大军浩浩荡荡,经过近十年的发展,Android技术优化日异月新,如今Android 11.0 已经发布,Android系统性能也已经非常流畅,可以在体验上完全媲美iOS. 但是,到了各大厂商手 ...

  6. 《Linux 性能优化实战—倪朋飞 》学习笔记 CPU 篇

    平均负载 指单位时间内,系统处于可运行状态和不可中断状态的平均进程数,即平均活跃进程数 可运行状态:正在使用CPU或者正在等待CPU 的进程,也就是我们常用 ps 命令看到的,处于 R 状态 (Run ...

  7. 技术博客--微信小程序canvas实现图片编辑

    技术博客--微信小程序canvas实现图片编辑 我们的这个小程序不仅仅是想给用户提供一个保存和查找的平台,还希望能给用户一个展示自己创意的舞台,因此我们实现了图片的编辑部分.我们对对图片的编辑集成了很 ...

  8. 原创:WeZRender:微信小程序Canvas增强组件

    WeZRender是一个微信小程序Canvas增强组件,基于HTML5 Canvas类库ZRender. 使用 WXML: <canvas style="width: 375px; h ...

  9. Go 程序的性能优化及 pprof 的使用

    Go 程序的性能优化及 pprof 的使用 程序的性能优化无非就是对程序占用资源的优化.对于服务器而言,最重要的两项资源莫过于 CPU 和内存.性能优化,就是在对于不影响程序数据处理能力的情况下,我们 ...

随机推荐

  1. Hadoop_简介_01

    1. Apache Hadoop 1.1 Hadoop介绍 Hadoop是Apache旗下的一个用java语言实现的开源软件框架, 是一个开发和运行处理大规模数据的软件平台. 允许使用简单的编程模型在 ...

  2. Maven项目使用mybatis报错 org.apache.ibatis.binding.BindingException: Invalid bound statement (not found):

    maven项目使用mybatis时,找不到mapper文件(.xml) 错误信息提示: 项目可以正常运行,但是在有请求到达服务器时(有访问数据库的请求),会出现报错!! 错误原因: mybatis没有 ...

  3. 关于Redis 二进制内容的 可视化尝试

    二进制内容的 能否可视化?  网上的资料比较少啊! -------------------------------------------------------------------------- ...

  4. 东芝开发板驱动OLED模块显示LOGO图片

    前言 在之前的两篇评测文章: 使用系统定时器SysTick实现精确延时微秒和毫秒函数 东芝MCU实现位带操作 介绍了系统SysTick实现精确延时,GPIO的输入输出使用,并实现了位带方式操作GPIO ...

  5. How to: Initialize Business Objects with Default Property Values in Entity Framework 如何:在EF中用默认属性值初始化业务对象

    When designing business classes, a common task is to ensure that a newly created business object is ...

  6. Cesium专栏-卫星轨迹

    Cesium Cesium 是一款面向三维地球和地图的,世界级的JavaScript开源产品.它提供了基于JavaScript语言的开发包,方便用户快速搭建一款零插件的虚拟地球Web应用,并在性能,精 ...

  7. 产品经理如何使用 CODING 进行项目规划

    CODING 为您的企业提供从概念到软件开发再到产品发布的全流程全周期软件研发管理,为您的研发团队提供全程助力,帮助研发团队捋清需求.不断迭代.快速反馈并能实时追踪项目进度直到完成.同时 CODING ...

  8. SpringCloudGateway开发详解

    路由简介: SpringCloudGateWay 是用于替代zuul作为API网关,在gateway中有三个重要的名词:过滤器,断言,路由 过滤器与断言是路由的一部分,路由便是将请求进行一系列的处理后 ...

  9. Python selenium登录163邮箱示例

    思路:使用python自带的unittest单元测试框架测试163邮箱登录成功的case import unittestfrom selenium import webdriverimport tim ...

  10. python操作excel表

    1.新增表并添加数据: 2.给工作表添加表名称,给表数据添加格式: import xlsxwriterdatas=(['Rent',1000], ['Gas',100], ['fish','画画'], ...