首页 > 其他分享 >canvas性能优化——离屏渲染

canvas性能优化——离屏渲染

时间:2023-02-14 17:22:05浏览次数:59  
标签:canvas 粒子 particle 渲染 ctx radius 离屏

一、正常动画实践

为了使用户达到更好的体验,做动画的时候都知道用requestAnimationFrame了,但是他也是有极限的,当绘制的东西足够多或者复杂的时候,频繁的删除与重绘降低了很多性能。

在canvas中粒子系统应该算是比较常见的一种了,现在创建一个canvas画布,并绘制100个粒子在整个画布上由上至下做匀速往返直线运动。

1.创建一个场景类,并初始化基本数据

复制代码
class Scene {
    constructor(canvas) {
        this.canvas = canvas;
        this.width = this.canvas.width = 800;
        this.height = this.canvas.height = 500;
        this.ctx = this.canvas.getContext('2d');

        this.amount = 100; // 粒子总数量
        this.radius = 5; // 粒子半径
        this.particles = []; // 粒子集合
        this.speed = 10; // 粒子速度

        this.init();
    }
}
复制代码

2.创建一个方法绘制粒子

复制代码
/* 绘制一个粒子
 * ctx —— canvas上下文
 * x —— 圆心x坐标
 * y —— 圆心y坐标
 * r —— 圆半径
 */
drawParticle(ctx, x, y, r) {
    ctx.fillStyle = 'black';
    ctx.beginPath();
    ctx.arc(x, y, r, 0, 2 * Math.PI);
    ctx.closePath();
    ctx.fill();
}
复制代码

3.初始化所有粒子

上面初始化Scene类是调用的this.init()

复制代码
init() {
    this.particles = [];

    // 随机位置生成粒子
    for (let i = 0; i < this.amount; i++) {
        let rx = Math.floor(this.radius + Math.random() * (this.width - this.radius * 2));
        let ry = Math.floor(this.radius + Math.random() * (this.height - this.radius * 2));

        this.particles.push({
            x: rx,
            y: ry,
            isMax: false // 是否达到边界
        });
        this.drawParticle(this.ctx, rx, ry, this.radius);
    }

    // 动画
    this.animate();
}
复制代码

4.运动动画

复制代码
animate() {
    this.ctx.clearRect(0, 0, this.width, this.height);

    for (let i = 0; i < this.particles.length; i++) {
        let particle = this.particles[i];

        // 判断是是否到达边界
        if (particle.isMax) {
            particle.y -= this.speed;
            if (particle.y <= 0 + this.radius) {
                particle.isMax = false;
                particle.y += this.speed;
            }
        } else {
            particle.y += this.speed;
            if (particle.y >= this.height - this.radius) {
                particle.isMax = true;
                particle.y -= this.speed;
            }
        }

        // 重绘
        this.drawParticle(this.ctx, particle.x, particle.y, this.radius);
    }

    let self = this;
    requestAnimationFrame(() => {
            self.animate();
    });
}
复制代码

5.分析

从上面最终展示的效果来看100个还是很流畅的,我试着增加数量到1000,发现依然没问题,fps很稳定在60左右,如下图:
1000个粒子

接着增加数量到4000,发现fps已经开始变化了,稳定在50左右,数量越多越明显,如下图。

4000个粒子

二、离屏渲染

1.为什么使用离屏渲染 && 离屏渲染是什么

正如上文所说,当粒子量级达到一定数量的时候,性能开始降低,帧率开始下降,这是我们不想看到的,因为这很影响用户体验。

离屏渲染到底是什么?在Mozilla文档上有简单介绍,大概意思就是说再创建一个新的canvas,然后将要绘制的图形,先在新的canvas中绘制,然后使用drawImage()将新的canvas画到当前的canvas上。网上看了一些也没有讲的特别清楚,也动手实践了一下。

 

2.创建粒子(圆形)类

复制代码
class Particle {
    constructor(r) {
        this.canvas = document.createElement('canvas'); // 创建一个新的canvas
        this.width = this.canvas.width = r * 2; // 创建一个正好包裹住一个粒子canvas
        this.height = this.canvas.height = r * 2;
        this.ctx = this.canvas.getContext('2d');
        this.x = this.width / 2;
        this.y = this.height / 2;
        this.r = r; // 半径

        this.create();
    }

    // 创建粒子
    create() {
        this.ctx.save();
        this.ctx.fillStyle = 'black';
        this.ctx.beginPath();
        this.ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
        this.ctx.closePath();
        this.ctx.fill();
        this.ctx.restore();
    }

    // 移动粒子
    move(ctx, x, y) {
        // 将这个新创建的canvas画到真正显示的canvas上
        ctx.drawImage(this.canvas, x, y);
    }
}
复制代码

3.初始化小球以及动画代码

复制代码
// init方法中的for循环
for (let i = 0; i < this.amount; i++) {
    let rx = Math.floor(this.radius + Math.random() * (this.width - this.radius * 2));
    let ry = Math.floor(this.radius + Math.random() * (this.height - this.radius * 2));

    // 每一个粒子创建一个实例
    let particle = new Particle(this.radius);

    this.particles.push({
        instance: particle,
        x: rx,
        y: ry,
        isMax: false
    });
}
复制代码

animate()方法中原本调用drawParticle()的地方改为:

// this.drawParticle(this.ctx, particle.x, particle.y, this.radius);
//            ↓↓
particle.instance.move(this.ctx, particle.x, particle.y);

4.分析

通过上面的改造,效果如下图:
2000个粒子

 

但是,发现在2000个粒子的时候fps已经降低到25左右,上面没有使用离屏渲染的时候4000个粒子fps还有50,由此感觉离屏渲染反而降低了性能。

三、离屏渲染——代码优化

1.优化思路

在上面相当于创建了跟粒子个数相同的离屏canvas,而且每个都是一样的,既然都是一样的,那干脆就只new一个粒子的实例出来,这样就只创建了一个离屏的canvas,然后通过在不同位置多次绘制这个离屏canvas,实现动画效果。

2.代码修改

修改init方法,将循环中创建实例的方法放到循环外面。

复制代码
// 只创建一个实例
let particle = new Particle(this.radius);

for (let i = 0; i < this.amount; i++) {
    let rx = Math.floor(this.radius + Math.random() * (this.width - this.radius * 2));
    let ry = Math.floor(this.radius + Math.random() * (this.height - this.radius * 2));

    this.particles.push({
        instance: particle,
        x: rx,
        y: ry,
        isMax: false
    });
}
复制代码

3.分析

优化了代码之后,果然达到了预期的效果,下面是4000个粒子的fps如下图:

四、写在后面

通过上面的操作,发现离屏渲染虽然可以优化动画的性能,但是从上面可以看出频繁的创建和销毁大量canvas也会很影响性能的,所以这中间要有一个取舍。另外,凡事都有一个限度,离屏渲染也不是万能的,有兴趣的可以试试,在这个例子中,如果粒子数量达到7000、8000或者9000乃至更多其实还是有很明显的卡顿。

当然,canvas的一些api也是消耗性能的,所以最后发现,要做好性能优化,首先代码肯定是要优化,另外就是使用像离屏渲染之类的方法。

转自:https://blog.csdn.net/qq_26733915/article/details/81675124

回过头来一想,都说离屏渲染提升性能,难道是操作有问题,接着又进行了一波优化。4000个粒子

标签:canvas,粒子,particle,渲染,ctx,radius,离屏
From: https://www.cnblogs.com/kn-zheng/p/17120260.html

相关文章

  • Openlayers 通过canvas渲染部分地图
    效果图实现原理简单描述Openlayers图层的渲染大多数都是通过canvas实现,在图层渲染前后事件中通过canvas控制渲染区域即可实现。代码点击查看代码componentDidMoun......
  • Android 中Canvas的save(),saveLayer()和restore()解析
    1、save()方法:用来保存Canvas的状态,save()方法之后的代码,可以调用Canvas的平移、放缩、旋转、裁剪等操作!2、restore()方法:用来恢复Canvas之前保存的状态(可以想成是......
  • vue-day02——插值语法、文本指令、属性指令、事件指令、class和style、条件渲染、列
    目录昨日回顾今日内容1插值语法1.1mvvm演示1.1插值语法2文本指令3属性指令4事件指令5class和style6条件渲染7列表渲染补充:作业昨日回顾#1put,post提交的jso......
  • python canvas画布的介绍
    1、在画布上绘制对象,通常用create_xxxx,xxxx=对象类型。2、每次调用create_xxx,都会返回创建组件的ID,也可以用tag属性指定标签。3、通过调用canvas.move实现一次性动作。实例#......
  • UE4/UE5移动端延迟渲染
    移动端延迟渲染的支持情况①UE4.26不支持OpenGLES,使用4个inputattachments②UE4.27将inputattachments降到了3个,可以支持更多的设备。   另外增加了更多功能......
  • 使用PHP和jq一起渲染页面时,可根据接口返回值改变php渲染的数据状态
    /*收藏院校1-专业2-文章3*//**e:this,当前对象*_id:接口请求所需的id(收藏时传递当前列表的id,取消收藏时传递收藏后的列表id(接口会返回))*_title:'收藏的列表标题'......
  • HTML5 canvas画一个折线图
    <body><canvasid="drawing"style="border:1pxsolid;"width="600"height="400"></canvas><scripttype="text/javascript">letdrawing=document.query......
  • HTML5 canvas基础使用
    <body> <!-- 1,canvas的宽和高要在标签中写,写在style中将会使画布拉伸到指定宽和高,不是真正的宽高。 --> <canvasid='drawing'width="300"height="700"......
  • vue原理:diff、模板编译、渲染过程等
    一、虚拟DOM:因为DOM操作非常消耗性能,在操作DOM时,会出现DOM的回流(Reflow:元素大小或者位置发生改变)与重绘(元素样式的改变)使DOM重新渲染。现在的框架Vue和React很少直接操作......
  • echarts 从后端获取数据,动态渲染图表
    echarts从后端获取数据,动态渲染图表简介echarts的数据是在初始化后setOption中直接填入的,但是很多时候可能数据需要异步加载后再填入。ECharts中实现异步数据的更新非......