在Canvas中渲染粒子,提高FireFox的FPS

Rendering particles in Canvas, improving FireFox FPS

本文关键字:提高 FireFox FPS 粒子 Canvas      更新时间:2023-09-26

我试图在Firefox中获得30以上的FPS。我真的不知道我还能做些什么来改善它。有什么建议吗?

Chrome和Opera徘徊在60 fps左右,而FF停留在10-20之间,正因为如此,它导致我的页面其余部分出现延迟问题。

http://jsfiddle.net/7vv2tur7/

window.requestAnimFrame = (function(){
    return window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    function( callback ) {
        window.setTimeout(callback, 1000 / 60);
    };
})();

(function() {
    particleCanvas();
    con = particle.getContext('2d');
    pxs = [];
    for(var i = 0; i < 25; i++) {
        pxs[i] = new Circle();
        pxs[i].reset();
    }
    requestAnimFrame(paintParticles);
    window.onresize = function(event) {
        particleCanvas();
    };
})();
function Circle() {
    // settings
    this.s = {
        ttl:10000,
        // speeds
        xmax:4,
        ymax:4,
        // max size
        size:1,
        rt:4,
        xdrift:60,
        ydrift:60,
        opacity: 0.3,
    };

    this.reset = function() {
        // randomise positioning for each particle
        this.x = particle.width * Math.random();
        this.y = particle.height * Math.random();
        // size
        this.r = ((this.s.size-1)*Math.random()) + 1;

        this.dx = (Math.random()*this.s.xmax) * (Math.random() < 0.5 ? -1 : 1);
        this.dy = (Math.random()*this.s.ymax) * (Math.random() < 0.5 ? -1 : 1);
        this.hl = this.s.ttl / 4 * (this.r/this.s.size);
        this.rt = Math.random()*this.hl;
        this.s.rt = Math.random() +1;
        this.stop = Math.random();
        this.s.xdrift *= Math.random() * (Math.random() < 0.5 ? -1 : 1);
        this.s.ydrift *= Math.random() * (Math.random() < 0.5 ? -1 : 1);
    };
    this.fade = function() {
        this.rt += 5 + this.s.rt;
    };
    this.draw = function() {
        if(this.rt >= this.hl) this.reset();
        var newo = 1 - (this.rt/this.hl);

        con.globalAlpha = this.s.opacity;

        con.beginPath();
        con.arc(this.x,this.y,this.r,0,Math.PI*2,true);
        con.closePath();
        var cr = this.r*newo;
        g = con.createRadialGradient(this.x, this.y, 0, this.x, this.y, (cr <= 0 ? 1 : 5));
        g.addColorStop(0.0, 'rgba(255,255,255,' + newo + ')');
        g.addColorStop(this.stop, 'rgba(255,255,255,' + 0.5 * newo + ')');
        g.addColorStop(1.0, 'rgba(255,255,255, 0)');

        con.fillStyle = g;
        con.fill();
    };
    this.move = function() {
        this.x += (this.rt/this.hl)*this.dx;
        this.y += (this.rt/this.hl)*this.dy;
        if(this.x > particle.width || this.x < 0) this.dx *= -1;
        if(this.y > particle.height || this.y < 0) this.dy *= -1;
    };
    this.getX = function() { return this.x; };
    this.getY = function() { return this.y; };
}
function particleCanvas() {
    particle.width  = document.querySelector('.start').offsetWidth / 2;
    particle.height = document.querySelector('.start').offsetHeight / 2;
}
function paintParticles() {
    requestAnimFrame(paintParticles);
    con.clearRect ( 0 , 0 , particle.width, particle.height );
    for(var i = 0; i < pxs.length; i++) {
        pxs[i].fade();
        pxs[i].move();
        pxs[i].draw();
    }
    var thisFrameFPS = 1000 / ((now=new Date()) - lastUpdate);
        fps += (thisFrameFPS - fps) / fpsFilter;
        lastUpdate = now;
}
var fps = 0, now,
    lastUpdate = (new Date())*1 - 1, fpsFilter = 50;

setInterval(function(){
    document.querySelector('.fps').innerHTML = fps.toFixed(1) + ' fps';
}, 1000);

你的性能杀手是在每个动画帧中为每个粒子创建渐变。

你可以把昂贵的gradient换成相对便宜的globalAlpha

这是一个使用globalAlpha而不是渐变的重构。我让你调整globalAlpha来匹配你的效果,但是这个重构在Firefox上要快得多(在我的机器上是59+)。

在你的代码中还有其他可能的优化,但是使用梯度是性能杀手…

window.requestAnimFrame = (function(){
  return window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    function( callback ) {
    window.setTimeout(callback, 1000 / 60);
  };
})();
(function() {
  particleCanvas();
  con = particle.getContext('2d');
  pxs = [];
  for(var i = 0; i < 25; i++) {
    pxs[i] = new Circle();
    pxs[i].reset();
  }
  requestAnimFrame(paintParticles);
  window.onresize = function(event) {
    particleCanvas();
  };
})();
function Circle() {
  // settings
  this.s = {
    ttl:10000,
    // speeds
    xmax:4,
    ymax:4,
    // max size
    size:1,
    rt:4,
    xdrift:60,
    ydrift:60,
    opacity: 0.7,
  };
  this.reset = function() {
    // randomise positioning for each particle
    this.x = particle.width * Math.random();
    this.y = particle.height * Math.random();
    // size
    this.r = ((this.s.size-1)*Math.random()) + 1;
    this.dx = (Math.random()*this.s.xmax) * (Math.random() < 0.5 ? -1 : 1);
    this.dy = (Math.random()*this.s.ymax) * (Math.random() < 0.5 ? -1 : 1);
    this.hl = this.s.ttl / 4 * (this.r/this.s.size);
    this.rt = Math.random()*this.hl;
    this.s.rt = Math.random() +1;
    this.stop = Math.random();
    this.s.xdrift *= Math.random() * (Math.random() < 0.5 ? -1 : 1);
    this.s.ydrift *= Math.random() * (Math.random() < 0.5 ? -1 : 1);
    this.s.opacity=0.70;
  };
  this.fade = function() {
    this.rt += 5 + this.s.rt;
    this.s.opacity-=.005;
  };
  this.draw = function() {
    if(this.rt >= this.hl) this.reset();
    var newo = 1 - (this.rt/this.hl);
    con.globalAlpha = this.s.opacity;
    con.beginPath();
    con.arc(this.x,this.y,this.r,0,Math.PI*2,true);
    con.closePath();
    var cr = this.r*newo;
    con.fill();
  };
  this.move = function() {
    this.x += (this.rt/this.hl)*this.dx;
    this.y += (this.rt/this.hl)*this.dy;
    if(this.x > particle.width || this.x < 0) this.dx *= -1;
    if(this.y > particle.height || this.y < 0) this.dy *= -1;
  };
  this.getX = function() { return this.x; };
  this.getY = function() { return this.y; };
}
function particleCanvas() {
  particle.width 	= document.querySelector('body').offsetWidth / 2;
  particle.height = document.querySelector('body').offsetHeight / 2;
}
function paintParticles() {
  requestAnimFrame(paintParticles);
  con.clearRect ( 0 , 0 , particle.width, particle.height );
  con.fillStyle = 'white';
  for(var i = 0; i < pxs.length; i++) {
    pxs[i].fade();
    pxs[i].move();
    pxs[i].draw();
  }
  var thisFrameFPS = 1000 / ((now=new Date()) - lastUpdate);
  fps += (thisFrameFPS - fps) / fpsFilter;
  lastUpdate = now;
}
var fps = 0, now,
    lastUpdate = (new Date())*1 - 1, fpsFilter = 50;
setInterval(function(){
  document.querySelector('.fps').innerHTML = fps.toFixed(1) + ' fps';
}, 1000);
html,
body {height:100%;width:100%;display:block;margin:0;padding:0;background:black}
* {box-sizing:border-box}
span {
  position:absolute;
  top:50px;
  left:50px;
  font-size:28px;
  color:white;
  font-family:mono;
}
canvas {width:100%;height:100%}    </style>
<canvas id="particle"></canvas>
<span class="fps"></span>

我也在努力解决这个延迟,缓慢,仅在Firefox上的fps问题。Chrome, safari或ie浏览器都可以。

我有办法了!

问题是你通过N个粒子循环,每次绘制,这N个绘制是我们在Firefox中遇到的问题。我们每帧只画一次就是解决方案。

在每一帧(myRequestAnimationFrame, Polyfills等)

const particles = []; // WE SHOW THIS AS EXAMPLE ONLY - 1000 PARTICLES
const onRender = () => {
    myRequestAnimationFrame(onRender);
    ctx.clearRect(0, 0, canvas.width, canvas.height); // CLEAR CANVAS EACH FRAME
    ctx.beginPath(); // WE START
    for (var i = 0; i < particles.length; i++) {
        // SET EACH PARTICLE VALUES, INCLUDING THE X, Y
        ctx.moveTo(particles[i].x, particles[i].y); // THE KEY TO THE SOLUTION
        ctx.arc(particles[i].x, particles[i].y, particles[i].r, Math.PI * 2, false); // WE FILL EACH PARTICLE WITH COLOUR
    }
    ctx.fill(); // LASTLY WE DRAWN ONCE ONLY PER FRAME
};
myRequestAnimationFrame(onRender);

Bob's your uncle !

丹尼尔