canvas的主要功能就是用来绘制内容,有时候为了给用户流畅的视觉感受,需要绘制的频率要求很高,这样对绘制的性能就有要求,那么怎么才能写出高性能的绘制代码呢。

尽可能少调用api

例如我们绘制一段线条,如果用如下代码的话,每移动一次就stroke一次:

1      for (var i = 0; i < points.length - 1; i++) {
2 var p1 = points[i];
3 var p2 = points[i + 1];
4 context.beginPath();
5 context.moveTo(p1.x, p1.y);
6 context.lineTo(p2.x, p2.y);
7 context.stroke();
8 }

优化后代码如下,这样beginPah和stroke就少调用了n次。

1       context.beginPath();
2 for (var i = 0; i < points.length - 1; i++) {
3 var p1 = points[i];
4 var p2 = points[i + 1];
5 context.moveTo(p1.x, p1.y);
6 context.lineTo(p2.x, p2.y);
7 }
8 context.stroke();

尽量少改变CANVAS状态机

我们可以改变 context 的若干状态,而几乎所有的渲染操作,最终的效果与 context 本身的状态有关系。例如当对context.lineWidth赋值的话,开销远远大于对一个普通对象赋值的开销。

Canvas 上下文不是一个普通的对象,当调用了 context.lineWidth = 5 时,浏览器会需要立刻地做渲染上下文环境的工作,这样你下次调用诸如 stroke 或 strokeRect 等 API 时,画出来的线就正好是 5 个像素宽了。其实这也是浏览器自身的一种优化,否则如果等到stroke调用时再临时准备渲染环境,会更加影响正常绘制情况下的性能。

下面对比优化前后的代码:

      for (var i = 0; i < STRIPES; i++) {
context.fillStyle = (i % 2 ? COLOR1 : COLOR2);
context.fillRect(i * GAP, 0, GAP, 480);
}
      context.fillStyle = COLOR1;
for (var i = 0; i < STRIPES / 2; i++) {
context.fillRect((i * 2) * GAP, 0, GAP, 480);
}
context.fillStyle = COLOR2;
for (var i = 0; i < STRIPES / 2; i++) {
context.fillRect((i * 2 + 1) * GAP, 0, GAP, 480);
}

上面两段代码,对fillStyle的调用时机做了改变,提高了性能。

分层canvas

绘制场景复杂的情况下,一般采用多个canvas,可依据绘制内容的频率高低来划分。

如游戏中的背景绘制频率低可以放在一层canvas上,上面的小人等绘制频率高放在一层canvas上,两层canvas的叠加效果达到完整效果。

如下图中绘制过程中的圆形在一层canvas上,不断清除不断绘制,而下面的已经绘制出来的笔迹内容放在另外一层canvas上,不需要清除重绘。

离屏canvas

也叫作预渲染,在离屏canvas上绘制好一整块图形,绘制好后在放到视图canvas中,适合每一帧画图运算复杂的图形。

比如我们有时候为了尽可能少的请求网络资源,会用到精灵图,这样在绘制精灵图某一块内容时,需要利用绘图api的裁剪。

实际发现,使用 drawImage 绘制一张大尺寸图片到较小画布区域上,比起绘制一张和绘制区域尺寸一样大的图片的情形,开销要大一些。可以认为,两者相差的开销正是「裁剪」这一个操作的开销。下面三种绘制方式,性能开销依次增加。

// 将image放到目标canvas指定位置,大小按照原图大小渲染
void ctx.drawImage(image, dx, dy);
// 将image放到目标canvas指定位置,指定宽高渲染
void ctx.drawImage(image, dx, dy, dWidth, dHeight);
// 将image裁剪之后放到目标canvas指定位置,指定宽高渲染
void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

而离屏渲染就可以让我们先把图片裁剪成想要的尺寸内容保存起来,等到真正绘制的时候就可以使用第一种写法简单的把图片绘制出来。

// 在离屏 canvas 上绘制
var offscreencanvas = document.createElement('canvas');
// 宽高赋值为想要的图片尺寸
offscreencanvas.width = dWidth;
offscreencanvas.height = dHeight;
// 裁剪
offscreencanvas.getContext('2d').drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
// 在视图canvas中绘制
viewcontext.drawImage(canvas, x, y);

有时候,游戏对象是多次调用 drawImage 绘制而成,或者根本不是图片,而是使用路径绘制出的矢量形状,那么离屏绘制还能帮你把这些操作简化为一次 drawImage 调用。

组合图形组合了多个图形将它们绘制存放到离屏canvas中,下次未变化的时候直接绘制一次离屏canvas。

裁剪

Canvas (大小一般小于等于屏幕宽高)只是整个大场景下的一个「可视窗口」,如果我们在每一帧中,都把全部内容画出来,势必就会有很多东西画到 Canvas 外面去了,同样调用了绘制 API,但是并没有任何效果。

那么视口外的内容是不需要绘制的,但如果绘制对性能影响有多少呢?进行这样一个实验,绘制一张 320x180 的图片 104 次,当每次都绘制在 Canvas 内部时,消耗了 40ms,而每次都绘制在 Canvas 外时,仅消耗了 8ms。虽然绘制在canvas外时,消耗的时间较短。

但考虑到计算的开销与绘制的开销相差 2~3 个数量级,所以一般情况下通过计算来过滤掉哪些画布外的对象,仍然是很有必要的。

局部重绘

由于 Canvas 的绘制方式是画笔式的,在 Canvas 上绘图时每调用一次 API 就会在画布上进行绘制,一旦绘制就成为画布的一部分。绘制图形时并没有对象保存下来,一旦图形需要更新,需要清除整个画布重新绘制

如下图仅对红边框的平行四边形做改变,如果每次重绘整个画布内容就不太合适

Canvas 局部刷新的方案:

  1. 清除指定区域的颜色,并设置 clip
  2. 所有同这个区域相交的图形重新绘制

要实现局部渲染时,需要考虑的两个因素是:

  • 单次刷新时影响的范围最小
  • 刷新的图形不会影响其他图形的正确绘制

清除画布内容

有地方说这三种方法性能依次提高,我目前只是使用了clearRect(),没有做个实验对照。

context.fillRect()//颜色填充
context.clearRect(0, 0, w, h)
canvas.width = canvas.width; // 一种画布专用的技巧

坐标值尽量使用整数

避免使用浮点数坐标,使用非整数的坐标绘制内容,系统会自动使用抗锯齿功能,尝试对线条进行平滑处理,这又是一种性能消耗。

可以调用 Math.round 四舍五入取整。

避免大量计算造成阻塞

所谓「阻塞」,可以理解为不间断运行时间超过 16ms 的 JavaScript 代码,导致页面卡顿,丢帧,或者失去响应,这种问题能很快被用户察觉到,造成很差的交互体验。

所以我们要把与渲染无关的大量计算交给worker。大量计算可能造成渲染不流畅,但绝对不能让用户操作卡顿失去响应。

像下图的效果,需要计算大量函数曲线上的点来绘制成曲线,我们移动的时候可以看到计算新点坐标值的过程是有延迟的,但是并不会让用户鼠标拖拽卡顿失效,渲染的过程再跟随鼠标移动。

总结

以上便是总结到的提升绘制效率的几点建议!具体采用哪种需要在实际项目里面根据情况来定,如果你知道这几种方式至少不会大脑空白了!

还有几点开发过程需要注意的:

  • 尽可能使用计算代替canvas渲染
  • 减少改变 context 的状态,如果要改变请赋值正确的类型,减少浏览器的尝试
  • 减少使用 shadowBlur 效果,阴影渲染的性能开销通常比较高

canvas性能优化总结的更多相关文章

  1. 小程序Canvas性能优化实战

    以下内容转载自totoro的文章<小程序Canvas性能优化实战!> 作者:totoro 链接:https://blog.totoroxiao.com/canvas-perf-mini/ ...

  2. canvas的性能优化

    canvas玩多了后,就会自动的要开始考虑性能问题了.怎么优化canvas的动画呢? [使用缓存] 使用缓存也就是用离屏canvas进行预渲染了,原理很简单,就是先绘制到一个离屏canvas中,然后再 ...

  3. JavaScript性能优化

    如今主流浏览器都在比拼JavaScript引擎的执行速度,但最终都会达到一个理论极限,即无限接近编译后程序执行速度. 这种情况下决定程序速度的另一个重要因素就是代码本身. 在这里我们会分门别类的介绍J ...

  4. (转) Android开发性能优化简介

    作者:贺小令 随着技术的发展,智能手机硬件配置越来越高,可是它和现在的PC相比,其运算能力,续航能力,存储空间等都还是受到很大的限制,同时用户对手机的体验要求远远高于PC的桌面应用程序.以上理由,足以 ...

  5. Android应用性能优化(转)

    人类大脑与眼睛对一个画面的连贯性感知其实是有一个界限的,譬如我们看电影会觉得画面很自然连贯(帧率为24fps),用手机当然也需要感知屏幕操作的连贯性(尤其是动画过度),所以Android索性就把达到这 ...

  6. 移动H5前端性能优化指南

    移动H5前端性能优化指南 概述 1. PC优化手段在Mobile侧同样适用2. 在Mobile侧我们提出三秒种渲染完成首屏指标3. 基于第二点,首屏加载3秒完成或使用Loading4. 基于联通3G网 ...

  7. android 性能优化

    本章介绍android高级开发中,对于性能方面的处理.主要包括电量,视图,内存三个性能方面的知识点. 1.视图性能 (1)Overdraw简介 Overdraw就是过度绘制,是指在一帧的时间内(16. ...

  8. web前端性能优化指南(转)

    web前端性能优化指南 概述 1. PC优化手段在Mobile侧同样适用2. 在Mobile侧我们提出三秒种渲染完成首屏指标3. 基于第二点,首屏加载3秒完成或使用Loading4. 基于联通3G网络 ...

  9. Android性能优化典范第一季

    2015年伊始,Google发布了关于Android性能优化典范的专题,一共16个短视频,每个3-5分钟,帮助开发者创建更快更优秀的Android App.课程专题不仅仅介绍了Android系统中有关 ...

随机推荐

  1. Java基础语法:包机制

    为了更好地组织类,Java 提供了包(package)机制. 这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class).接口(interface).枚举(enumerations)和注释( ...

  2. linux之安装nginx

    nginx官网:http://nginx.org/en/download.html 1.安装nginx所需环境 a)  PCRE pcre-devel 安装 # yum install -y pcre ...

  3. Markdown的基本用法与下载

    Markdown的基本用法与下载typora 下载typora 1.在浏览器搜索typora 2.然后点进去 3.往下翻点击Download 4.看自己是什么系统然后在选择 5.选好系统以后再去去选择 ...

  4. Android中Context样式分析

    目录 1.样式定义以及使用 1.1.默认样式 1.2.样式定义及使用 1.3.当前样式下attr属性的获取 1.4.属性集合的定义与获取 2.Activity中Theme的初始化流程 2.1.系统调用 ...

  5. 封装fetch请求失败和超时再次请求

    转: 封装fetch请求失败和超时再次请求 function _fetch(fetch_promise, timeout) { var abort_fn = null; //这是一个可以被reject ...

  6. 解决springboot项目打成jar包部署到linux服务器后上传图片无法访问的问题

    前言:目前大三,自己也在学习和摸索的阶段.在和学校的同学一起做前后端分离项目的时候,我们发现将后端打包成jar,然后部署到服务器中通过java -jar xxx.jar运行项目以后,项目中存在文件上传 ...

  7. 剑指 Offer 53 - II. 0~n-1中缺失的数字 + 二分法

    剑指 Offer 53 - II. 0-n-1中缺失的数字 Offer_53 题目详情 java代码 package com.walegarrett.offer; /** * @Author Wale ...

  8. 【转载】关于grad_tensors的解惑

    转载:https://www.cnblogs.com/marsggbo/p/11549631.html 平常都是无脑使用backward,每次看到别人的代码里使用诸如autograd.grad这种方法 ...

  9. MVC模式从Controller返回内容协商格式(Json或者Xml)

    WebAPI默认的返回格式Json,但是MVC是View,如果在MVC的控制器中,想要返回Json格式该怎么操作呢 在MVC的控制器中返回json数据只需要然会JsonResult而不是ActionR ...

  10. python爬虫(正则取数据)读取表格内的基金代码后爬取基金最新净值,同时写到对应的表格中,基于最近一次购买净值计算出涨跌幅(名字有点长)

    最近基金跌的真够猛,虽说是定投,但大幅度下跌,有时候适当的增加定投数也是降低平均成本的一种方式 每天去看去算太费时间,写了个爬虫,让他自动抓数据后自动计算出来吧 实现逻辑: 1.创建了一个excel表 ...