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個粒子


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM