小知识,大挑战!本文正在参与 程序员必备小知识 创作活动 本文同时参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金
前言
大家好, 我是Fly哥 ,这是本月输出文章的第三篇了,每个月原创四篇这是我给自己的要求,不会辜负大家对公众号前端图形的喜欢。 好了现在进入今天的主题: canvas 性能优化, 读完本篇文章你可以学到下面:
- 哪些因素会影响canvas的性能
- 如何优化canvas
到底是什么因素影响了canvas
我们都知道浏览器上渲染动画 每一秒高达60帧,也就是1秒钟内我们完成60次图像绘制, 也就是每一帧图像的绘制时间其实就是(1000/ 60)。 如果在每一帧动画的时间小于 16.7 ms 辣么就会出现卡顿、丢帧。而canvas 其实是一个指令式绘图系统, 他通过绘图指令来完成绘图操作。 那么很容易想到两个很关键的因素:
- 绘制图形的个数
- 绘制图形的大小
很容易理解,假设绘制 绘制一个图形 几毫秒 那么如果绘制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(0, 0, WIDTH, HEIGHT)
draw()
requestAnimationFrame(update)
}
requestAnimationFrame(update)
绘制100个
绘制100 个小球的fps 还是能够稳定在30fps 的如图:
100个
绘制10000个
100个
绘制10000个小球的fps 掉帧的厉害 是真的卡。 所以也就验证了我们的猜想 ,canvas 的性能是和绘制图形个数有关系的
这个fps 帧率显示器是 chrome 自带的 输入 command + shift + p 搜索🔍 render fps 自然就找到了。
绘制图形的大小
我们再来验证绘制图形的大小会不会影响fps 。我将小球的数量改成1000, 半径改成10 。
目前的小球的数量 是1000 , 半径大小是10。
我们看下gif 图:
Oct-24-2021 20-23-40
我们看到帧率大概是稳定在30fps 的, 这时候 数量不变的情况下,我将小球的半径改成200, 我们在看下帧率变化,然后呢 你会看到帧率有较为明显的下跌。 如图:
Oct-24-2021 20-24-16
所以总结以上来看,影响canvas的因素就是有以上两点:
- 第一个渲染的图形数量
- 第二个就是渲染图形的大小
其实我来深度分析, 第一个渲染的图形数量多,就是调用绘图指令的次数比较多,
第二个渲染的图形大,就是一次绘图渲染的时间比较长
然后下面我就开始优化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 i= 0;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」, 目前在某电商公司互动游戏组。在这个互联网技术疯狂快速迭代的时代中,很高兴能和你一起变强!😉