你不知道的canvas ——性能优化篇(1)

9,415 阅读5分钟

小知识,大挑战!本文正在参与 程序员必备小知识 创作活动 本文同时参与 「掘力星计划」     ,赢取创作大礼包,挑战创作激励金

前言

大家好, 我是Fly哥 ,这是本月输出文章的第三篇了,每个月原创四篇这是我给自己的要求,不会辜负大家对公众号前端图形的喜欢。 好了现在进入今天的主题: canvas 性能优化, 读完本篇文章你可以学到下面:

  1. 哪些因素会影响canvas的性能
  2. 如何优化canvas

到底是什么因素影响了canvas

我们都知道浏览器上渲染动画 每一秒高达60帧,也就是1秒钟内我们完成60次图像绘制, 也就是每一帧图像的绘制时间其实就是(1000/ 60)。 如果在每一帧动画的时间小于 16.7 ms 辣么就会出现卡顿、丢帧。而canvas 其实是一个指令式绘图系统, 他通过绘图指令来完成绘图操作。 那么很容易想到两个很关键的因素:

  1. 绘制图形的个数
  2. 绘制图形的大小

很容易理解,假设绘制 绘制一个图形 几毫秒 那么如果绘制10000个图形呢?? 肯定时间就长了, 如果后面其他操作、ui交互、渲染其他图形... 这就会导致渲染的时间比较长了。canvas 绘制的图像都是一个个小像素点构成的、 你绘制一个半径为5 的圆 和半径为1000的圆所需要的像素点 肯定是不一样的。这里给大家看一张图清晰的明白一个绘制的构成。

图片

图片

你图形数量越大需要的像素点就越多,那么 片元着色器就要不断的去执行。

我写了两个小demo 来验证我们的猜想,纸上得来终觉浅,绝知此事要躬行哇!

绘制图形的个数

主要是绘制100个 和绘制10000个小球作对比

我们先看下代码:

      const canvas = document.getElementById('canvas')
      const ctx = canvas.getContext('2d')
      const WIDTH = canvas.width
      const HEIGHT = canvas.height

      function randomColor() {
        return (
          'rgb( ' +
          ((Math.random() * 255) >> 0) +
          ',' +
          ((Math.random() * 255) >> 0) +
          ',' +
          ((Math.random() * 255) >> 0) +
          ' )'
        )
      }
      function drawCirle(radius = 10) {
        const x = Math.random() * WIDTH
        const y = Math.random() * HEIGHT
        ctx.fillStyle = randomColor()
        ctx.beginPath()
        ctx.arc(x, y, radius, 0, Math.PI * 2)
        ctx.fill()
      }

      function draw(count = 1) {
        for (let i = 0; i < count; i++) {
          drawCirle()
        }
      }
      function update() {
        ctx.clearRect(00, WIDTH, HEIGHT)
        draw()
        requestAnimationFrame(update)
      }
      requestAnimationFrame(update)

绘制100个

绘制100 个小球的fps 还是能够稳定在30fps 的如图:

100个

100个

绘制10000个

100个

100个

绘制10000个小球的fps 掉帧的厉害 是真的卡。 所以也就验证了我们的猜想 ,canvas 的性能是和绘制图形个数有关系的

这个fps 帧率显示器是 chrome 自带的 输入 command + shift + p 搜索🔍 render fps 自然就找到了。

绘制图形的大小

我们再来验证绘制图形的大小会不会影响fps 。我将小球的数量改成1000, 半径改成10 。

目前的小球的数量 是1000半径大小是10

我们看下gif 图:

Oct-24-2021 20-23-40

Oct-24-2021 20-23-40

我们看到帧率大概是稳定在30fps 的, 这时候 数量不变的情况下,我将小球的半径改成200, 我们在看下帧率变化,然后呢 你会看到帧率有较为明显的下跌。 如图:

Oct-24-2021 20-24-16

Oct-24-2021 20-24-16

所以总结以上来看,影响canvas的因素就是有以上两点:

  1. 第一个渲染的图形数量
  2. 第二个就是渲染图形的大小

其实我来深度分析, 第一个渲染的图形数量多,就是调用绘图指令的次数比较多,

第二个渲染的图形大,就是一次绘图渲染的时间比较长

然后下面我就开始优化canvas

减少绘图指令的调用

这句话怎么理解呢 , 假设你要在场景中画正n变形,这是一个 很常见的需求可能你稍不注意写下了下面这几行代码:

 function drawAnyShape(points) {
          for(let i=0; i<points.length; i++) {
                const p1 = points[i]
                const p2 =  i=== points.length - 1 ? points[0] : points[i+1]
                ctx.fillStyle = 'black'
                ctx.beginPath();
                ctx.moveTo(...p1)
                ctx.lineTo(...p2)
                ctx.closePath();
                ctx.stroke()
          }
   }

points 对应的生成多边形的点,代码如下:

 function  generatePolygon(x,y,r, edges = 3) {
          const points = []
          const detla = 2* Math.PI / edges;
          for(let i0;i<edges;i++) {
              const theta = i * detla;
              points.push([x+ r * Math.sin(theta), y + r * Math.cos(theta)])
          }
          return points 
      }

主要是根据一个圆心,根据边数生成对应的点。

乍一看这代码没什么问题,生成一个正多边形, 这时候我在页面上画了1000个正多边形: 如下图:

优化前

优化前

一看这fps低成这个样子,很多人这时候说,你画的图形多,那我只要悄悄的改下代码,就能让fps 回归正常

我又重写了正多边形的方法:

function drawAnyShape2(points) {
      ctx.beginPath();
      ctx.moveTo(...points[0]);
      ctx.fillStyle = 'black'
      for(let i=1; i<points.length; i++) {
            ctx.lineTo(...points[i])
      }
      ctx.closePath();
      ctx.stroke()
  }

我们看下这时候的fps 帧率:

优化后

优化后

看了下fps 已经成功升到了30fps, 这是为什么呢, 第一段我们在循环中去做绘图操作, 循环一次, stoke() 一次,这显然是不合理的,第二个直接把stoke() ,放到循环外,其实就调用了一次,所以我们可以得出减少绘图指令是可以提高canvas的性能的

最后

关注我的公众号 「「前端图形」」,获取更多好玩与有趣的图形知识。「如果你也一样对技术热爱,喜欢「图形、数据可视化、游戏」📚并且为之着迷,欢迎加我个人微信(wzf582344150)「,将会邀请你加入我的」可视化交流学习群一起面向快乐编程~」 🦄。 「我是Fly」, 目前在某电商公司互动游戏组。在这个互联网技术疯狂快速迭代的时代中,很高兴能和你一起变强!😉