• canvas性能优化——离屏渲染


    一、正常动画实践

    为了使用户达到更好的体验,做动画的时候都知道用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个粒子

  • 相关阅读:
    希尔排序
    代理模式
    快速排序
    插入排序
    各种排序算法的稳定性和复杂度
    简单选择排序
    冒泡排序
    流程图
    PLAY学习【未完】
    项目之maven心得
  • 原文地址:https://www.cnblogs.com/vickylinj/p/14451781.html
Copyright © 2020-2023  润新知