小程序Canvas性能优化实战

栏目: Html5 · 发布时间: 6年前

内容简介:小程序中的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

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

  • 设置样式:setStrokeStyle, setFillStyle, setLineWidth, setFontSize
  • 绘制路线:moveTo, lineTo, stroke
  • 绘制站点:moveTo, arc, stroke, fill
  • 绘制图片:drawImage
  • 绘制文字:fillText

交互实现

实现交互主要步骤如下:

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

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

小程序Canvas性能优化实战

优化方法

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

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

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

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

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.48 ms,真机验证:移动稍快了一些,但画面仍有较高延迟。

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

减少绘制物

  1. 筛除视野外的绘制物:

    当用户在放大图像时,其实大部分绘制物都消失在了视野范围之外,避免绘制视野外的元素可以节省不必要的开销。点元素是比较容易判断是否在视野范围之外的,而站点、站点名、线路名都可以作为点元素处理;线路也可以计算出在视野范围内的部分线段,较为复杂,这里先不做处理。筛除掉视野外的绘制物之后测试一下,平均渲染时长 17.02 ms,真机验证:同上,没有太多变化。

  2. 筛除过小的绘制物:

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

小程序Canvas性能优化实战

降低重绘频率

虽然平均渲染时长已经低了很多,但是在交互时却仍有较高的延迟,这是因为每次 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)
},
复制代码

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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Open Data Structures

Open Data Structures

Pat Morin / AU Press / 2013-6 / USD 29.66

Offered as an introduction to the field of data structures and algorithms, Open Data Structures covers the implementation and analysis of data structures for sequences (lists), queues, priority queues......一起来看看 《Open Data Structures》 这本书的介绍吧!

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具