HTML Canvas 调整大小时的性能

HTML Canvas performance on resize

我设法将旧的 OO Processing sketch 翻译成 HTML5 Canvas: https://gist.github.com/sindiploma/ce1bb3b8424c32fb8d9e094f8944a789

我添加了mr.Doob的Stats library来记录canvas的表现。

它在文档加载时按预期执行,但在尝试调整大小时 window。帧率急剧下降。

关于如何提高调整大小事件的性能的任何提示?

您还可以查看此现场演示:

(function(f,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"===typeof define&&define.amd?define(e):f.Stats=e()})(this,function(){var f=function(){function e(a){c.appendChild(a.dom);return a}function u(a){for(var d=0;d<c.children.length;d++)c.children[d].style.display=d===a?"block":"none";l=a}var l=0,c=document.createElement("div");c.style.cssText="position:fixed;top:0;left:0;cursor:pointer;opacity:0.9;z-index:10000";c.addEventListener("click",function(a){a.preventDefault();
u(++l%c.children.length)},!1);var k=(performance||Date).now(),g=k,a=0,r=e(new f.Panel("FPS","#0ff","#002")),h=e(new f.Panel("MS","#0f0","#020"));if(self.performance&&self.performance.memory)var t=e(new f.Panel("MB","#f08","#201"));u(0);return{REVISION:16,dom:c,addPanel:e,showPanel:u,begin:function(){k=(performance||Date).now()},end:function(){a++;var c=(performance||Date).now();h.update(c-k,200);if(c>g+1E3&&(r.update(1E3*a/(c-g),100),g=c,a=0,t)){var d=performance.memory;t.update(d.usedJSHeapSize/
1048576,d.jsHeapSizeLimit/1048576)}return c},update:function(){k=this.end()},domElement:c,setMode:u}};f.Panel=function(e,f,l){var c=Infinity,k=0,g=Math.round,a=g(window.devicePixelRatio||1),r=80*a,h=48*a,t=3*a,v=2*a,d=3*a,m=15*a,n=74*a,p=30*a,q=document.createElement("canvas");q.width=r;q.height=h;q.style.cssText="width:80px;height:48px";var b=q.getContext("2d");b.font="bold "+9*a+"px Helvetica,Arial,sans-serif";b.textBaseline="top";b.fillStyle=l;b.fillRect(0,0,r,h);b.fillStyle=f;b.fillText(e,t,v);
b.fillRect(d,m,n,p);b.fillStyle=l;b.globalAlpha=.9;b.fillRect(d,m,n,p);return{dom:q,update:function(h,w){c=Math.min(c,h);k=Math.max(k,h);b.fillStyle=l;b.globalAlpha=1;b.fillRect(0,0,r,m);b.fillStyle=f;b.fillText(g(h)+" "+e+" ("+g(c)+"-"+g(k)+")",t,v);b.drawImage(q,d+a,m,n-a,p,d,m,n-a,p);b.fillRect(d+n-a,m,a,p);b.fillStyle=l;b.globalAlpha=.9;b.fillRect(d+n-a,m,a,g((1-h/w)*p))}}};return f});

var canvas = document.getElementById("inner_heading-canvas");

if (canvas.getContext) {
  var context = canvas.getContext("2d");
}

window.addEventListener("load", init, false);
window.addEventListener(
  "resize",
  function() {
    clearTimeout(init);
    setTimeout(init, 500);
  },
  false
);

// Init
function init() {
  var net = undefined;

  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;

  var nodesLength = Math.floor(canvas.width * canvas.height / 2000);

  // Nodes
  net = new Net();
  net.populate(nodesLength);

  window.requestAnimationFrame(render);

  function render() {
    net.update();
    net.draw();
    net.connect(50);
    window.requestAnimationFrame(render);
  }
}

// Net
class Net {
  constructor() {
    this.nodes = [];
    this.length = undefined;
  }

  populate(length) {
    this.length = length;

    for (var i = 0; i < length; i++) {
      var xPos = Math.floor(getRandom(0, canvas.width));
      var yPos = Math.floor(getRandom(0, canvas.height));
      this.nodes.push(new Node(xPos, yPos));
    }
  }

  update() {
    for (var i = 0; i < this.length; i++) {
      this.nodes[i].update();
    }
  }

  draw() {
    context.fillStyle = "#000000";
    context.fillRect(0, 0, canvas.width, canvas.height);

    for (var i = 0; i < this.length; i++) {
      this.nodes[i].draw();
    }
  }

  connect(distanceMax) {
    for (var i = 0; i < this.length - 1; i++) {
      this.nodes[i].connections = [];

      for (var j = 0; j < this.length - 1; j++) {
        var a = this.nodes[j].x - this.nodes[i].x;
        var b = this.nodes[j].y - this.nodes[i].y;
        var c = Math.sqrt(a * a + b * b);

        if (c < distanceMax) {
          this.nodes[i].connections.push(j);
        }
      }

      for (var k = 0; k < this.nodes[i].connections.length; k++) {
        context.beginPath();
        context.moveTo(this.nodes[i].x, this.nodes[i].y);
        context.lineTo(
          this.nodes[this.nodes[i].connections[k]].x,
          this.nodes[this.nodes[i].connections[k]].y
        );
        context.strokeStyle = "rgba(255,255,255,.15)";
        context.stroke();
      }
    }
  }
}

// Node
class Node {
  constructor(_x, _y) {
    this.x = _x;
    this.y = _y;
    this.radius = 2;
    this.depth = Math.floor(getRandom(1, 10)) / 10;
  }

  update() {
    var velocity = (1 - this.depth) / 10;
    this.x = this.x + velocity;

    if (this.x > canvas.width || this.x < 0) {
      this.x = 0;
    }
  }

  draw() {
    var alpha = 1 - this.depth;
    context.beginPath();
    context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI, false);
    context.fillStyle = "rgba(255,255,255," + alpha + ")";
    context.fill();
  }
}

// Helpers
function getRandom(min, max) {
  return Math.random() * (max - min) + min;
}

// Stats
var stats = new Stats();
stats.showPanel(0);
document.body.appendChild(stats.dom);

function animate() {
  stats.begin();
  stats.end();

  requestAnimationFrame(animate);
}

requestAnimationFrame(animate);
body {
  margin: 0;
}
<html>
    <head></head>
    <body>
        <canvas id="inner_heading-canvas" width="600" height="600"></canvas>
    </body>
</html>

您从清除 requestAnimationFrame 中获得的最重要的提升:

var rafID = null; //scoping requestAnimationFrame request-ID
//...
//inside resize-callback
window.cancelAnimationFrame(rafID);
...
//inside init() + inside render()
rafID = window.requestAnimationFrame(render); 

通过这种方式,我们确保浏览器不会仍在尝试更新旧实例。我将它添加到下面的代码片段中,我还确保超时有效(对于 debouncing)。

(function(f,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"===typeof define&&define.amd?define(e):f.Stats=e()})(this,function(){var f=function(){function e(a){c.appendChild(a.dom);return a}function u(a){for(var d=0;d<c.children.length;d++)c.children[d].style.display=d===a?"block":"none";l=a}var l=0,c=document.createElement("div");c.style.cssText="position:fixed;top:0;left:0;cursor:pointer;opacity:0.9;z-index:10000";c.addEventListener("click",function(a){a.preventDefault();
u(++l%c.children.length)},!1);var k=(performance||Date).now(),g=k,a=0,r=e(new f.Panel("FPS","#0ff","#002")),h=e(new f.Panel("MS","#0f0","#020"));if(self.performance&&self.performance.memory)var t=e(new f.Panel("MB","#f08","#201"));u(0);return{REVISION:16,dom:c,addPanel:e,showPanel:u,begin:function(){k=(performance||Date).now()},end:function(){a++;var c=(performance||Date).now();h.update(c-k,200);if(c>g+1E3&&(r.update(1E3*a/(c-g),100),g=c,a=0,t)){var d=performance.memory;t.update(d.usedJSHeapSize/
1048576,d.jsHeapSizeLimit/1048576)}return c},update:function(){k=this.end()},domElement:c,setMode:u}};f.Panel=function(e,f,l){var c=Infinity,k=0,g=Math.round,a=g(window.devicePixelRatio||1),r=80*a,h=48*a,t=3*a,v=2*a,d=3*a,m=15*a,n=74*a,p=30*a,q=document.createElement("canvas");q.width=r;q.height=h;q.style.cssText="width:80px;height:48px";var b=q.getContext("2d");b.font="bold "+9*a+"px Helvetica,Arial,sans-serif";b.textBaseline="top";b.fillStyle=l;b.fillRect(0,0,r,h);b.fillStyle=f;b.fillText(e,t,v);
b.fillRect(d,m,n,p);b.fillStyle=l;b.globalAlpha=.9;b.fillRect(d,m,n,p);return{dom:q,update:function(h,w){c=Math.min(c,h);k=Math.max(k,h);b.fillStyle=l;b.globalAlpha=1;b.fillRect(0,0,r,m);b.fillStyle=f;b.fillText(g(h)+" "+e+" ("+g(c)+"-"+g(k)+")",t,v);b.drawImage(q,d+a,m,n-a,p,d,m,n-a,p);b.fillRect(d+n-a,m,a,p);b.fillStyle=l;b.globalAlpha=.9;b.fillRect(d+n-a,m,a,g((1-h/w)*p))}}};return f});

var canvas = document.getElementById("inner_heading-canvas");

var timeoutID = null; 
var rafID = null;

if (canvas.getContext) {
  var context = canvas.getContext("2d");
}

window.addEventListener("load", init, false);
window.addEventListener(
  "resize",
  function() {
    clearTimeout(timeoutID);
    window.cancelAnimationFrame(rafID);
    timeoutID = setTimeout(init, 500);
  },
  false
);

// Init
function init() {
  var net = undefined;

  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;

  var nodesLength = Math.floor(canvas.width * canvas.height / 2000);

  // Nodes
  net = new Net();
  net.populate(nodesLength);

  rafID = window.requestAnimationFrame(render);

  function render() {
    net.update();
    net.draw();
    net.connect(50);
    rafID = window.requestAnimationFrame(render);
  }
}

// Net
class Net {
  constructor() {
    this.nodes = [];
    this.length = undefined;
  }

  populate(length) {
    this.length = length;

    for (var i = 0; i < length; i++) {
      var xPos = Math.floor(getRandom(0, canvas.width));
      var yPos = Math.floor(getRandom(0, canvas.height));
      this.nodes.push(new Node(xPos, yPos));
    }
  }

  update() {
    for (var i = 0; i < this.length; i++) {
      this.nodes[i].update();
    }
  }

  draw() {
    context.fillStyle = "#000000";
    context.fillRect(0, 0, canvas.width, canvas.height);

    for (var i = 0; i < this.length; i++) {
      this.nodes[i].draw();
    }
  }

  connect(distanceMax) {
    for (var i = 0; i < this.length - 1; i++) {
      this.nodes[i].connections = [];

      for (var j = 0; j < this.length - 1; j++) {
        var a = this.nodes[j].x - this.nodes[i].x;
        var b = this.nodes[j].y - this.nodes[i].y;
        var c = Math.sqrt(a * a + b * b);

        if (c < distanceMax) {
          this.nodes[i].connections.push(j);
        }
      }

      for (var k = 0; k < this.nodes[i].connections.length; k++) {
        context.beginPath();
        context.moveTo(this.nodes[i].x, this.nodes[i].y);
        context.lineTo(
          this.nodes[this.nodes[i].connections[k]].x,
          this.nodes[this.nodes[i].connections[k]].y
        );
        context.strokeStyle = "rgba(255,255,255,.15)";
        context.stroke();
      }
    }
  }
}

// Node
class Node {
  constructor(_x, _y) {
    this.x = _x;
    this.y = _y;
    this.radius = 2;
    this.depth = Math.floor(getRandom(1, 10)) / 10;
  }

  update() {
    var velocity = (1 - this.depth) / 10;
    this.x = this.x + velocity;

    if (this.x > canvas.width || this.x < 0) {
      this.x = 0;
    }
  }

  draw() {
    var alpha = 1 - this.depth;
    context.beginPath();
    context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI, false);
    context.fillStyle = "rgba(255,255,255," + alpha + ")";
    context.fill();
  }
}

// Helpers
function getRandom(min, max) {
  return Math.random() * (max - min) + min;
}

// Stats
var stats = new Stats();
stats.showPanel(0);
document.body.appendChild(stats.dom);

function animate() {
  stats.begin();
  stats.end();

  requestAnimationFrame(animate);
}

requestAnimationFrame(animate);
body {
  margin: 0;
}
<html>
    <head></head>
    <body>
        <canvas id="inner_heading-canvas" width="600" height="600"></canvas>
    </body>
</html>

除了这个快速修复之外,还有许多其他通用方法可以使调整大小效果更好。通过在 Whosebug 上搜索,可以学习一些技巧:

  • 改变/resolution