zoukankan      html  css  js  c++  java
  • 小程序Canvas性能优化实战

    以下内容转载自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机型有轻微卡顿,且随着使用时长卡顿逐渐明显,后期可以深入研究下是否有内存管理的问题。

  • 相关阅读:
    Windows Server 2003 SP2(32位) 中文版 下载地址 光盘整合方法
    用Recycle()方法对Java对象的重要性
    Lotus中千奇百怪的 $$
    Developing a simple application using steps "User Decision" and "Mail"(1) 沧海
    沟通中的情绪管理(演讲稿) 沧海
    人只有在压力之下,才可能成功,没做一件事,都必须成功,不许言败 沧海
    什么是IDOC,以及IDOC的步骤 沧海
    VS2008 Professional Edition CHS中的deffactory.dat读取错误 沧海
    Including custom text in the step "User Decision" 沧海
    SAP Upgrade Strategy 沧海
  • 原文地址:https://www.cnblogs.com/TencentLBS/p/12029177.html
Copyright © 2011-2022 走看看