javascript - Starfield canvas程序占用CPU过多

标签 javascript html canvas cpu-usage

我使用 canvas 创建了一个星空,它按预期工作:

<!DOCTYPE HTML5>

<html>
    <head>
        <title>StarField</title>
        <style>
            * {
                margin: 0;
                padding: 0;
            }
            body {
                width: 100%;
                height: 100%;
            }
        </style>
    </head>

    <body onLoad="startGame()"></body>

    <script type="text/javascript">
        var NO_OF_STARS = 512;
        var stars = [];

        function startGame() {          
            gameArea.start(); /* Makes the canvas */

            gameRun = requestAnimationFrame(gameArea.update); /* Starts the game and coordinates all the animations */

            window.addEventListener("keydown", function(e) {
                if(e.keyCode == 27) { /* ESC stops everything */
                    stopEverything();
                }
            });
        }

        var gameArea = {
            canvas : document.createElement("canvas"),
            start : function() {
                document.body.appendChild(this.canvas);

                this.canvas.width = document.body.clientWidth;
                this.canvas.height = document.body.clientHeight;
            },
            update : function() {               
                gameArea.clear(); /* Fills the canvas with #000000 */
                gameArea.drawStars(); /* Draws the stars */

                gameRun = requestAnimationFrame(gameArea.update); /* Repeat the whole thing */
            },
            drawStars : function() {
                var ctx = gameArea.canvas.getContext("2d");

                if(stars.length == 0) {
                    for(var i = 0; i < NO_OF_STARS; i++) {
                        var opacity = ((Math.floor(Math.random() * 10) / 10) + .1);

                        stars.push([getRandomInt(0, gameArea.canvas.width - 1), getRandomInt(0, gameArea.canvas.height - 1),opacity]);

                        ctx.beginPath();
                        ctx.strokeStyle = "rgba(255, 255, 255, " + opacity + ")";
                        ctx.moveTo(stars[i][0], stars[i][1]);
                        ctx.lineTo(stars[i][0] + 1, stars[i][1] + 1);
                        ctx.stroke();
                    }
                } else {
                    for(var i = 0; i < NO_OF_STARS; i++) {
                        ctx.strokeStyle = "rgba(255, 255, 255, " + stars[i][2] + ")";

                        stars[i][0] -= ((stars[i][2] == 1.0) ? 5 :
                                        (stars[i][2] >= 0.8) ? 4 :
                                        (stars[i][2] >= 0.5) ? 3 :
                                        (stars[i][2] >= 0.3) ? 2 :
                                                               1);

                        if(stars[i][0] < 0) {
                            var opacity = ((Math.floor(Math.random() * 10) / 10) + .1);
                            stars.splice(i, 1, [gameArea.canvas.width, getRandomInt(0, gameArea.canvas.height - 1), opacity]);
                        }

                        ctx.beginPath();
                        ctx.moveTo(stars[i][0], stars[i][1]);
                        ctx.lineTo(stars[i][0] + 1, stars[i][1] + 1);
                        ctx.stroke();
                    }
                }
            },
            clear : function() {
                var ctx = this.canvas.getContext("2d");
                ctx.fillStyle = "#000000";
                ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
            }
        };

        /**
         * Returns a random integer between min (inclusive) and max (inclusive)
         */
        function getRandomInt(min, max) {
            return Math.floor(Math.random() * (max - min + 1)) + min;
        }

        function stopEverything() {
            cancelAnimationFrame(gameRun);
        }

    </script>

</html>

这里的问题是它会占用大量 CPU(在配备 AMD A8 四核处理器的笔记本电脑上占用 60% 到 65%)。我希望这个 Canvas 程序也能在其他具有低端处理器的计算机上运行。

我已经尝试减少 NO_OF_STARS 但这不会改变 CPU 使用率。然而,当增加它时,动画速度会大大减慢并且 CPU 使用率会降低(不过我认为我不会增加它,所以这并不真正相关)

我还注意到 Canvas 的大小在 CPU 使用率方面起着重要作用。 (我上面提到的笔记本电脑的分辨率为 1366x768)但我希望 Canvas 占据整个视口(viewport)。

那么,如何降低 CPU 使用率?

最佳答案

为每颗星定义路径、笔划样式并将其栅格化是非常昂贵的。尝试收集一些操作以减少负载 - 这都是为了做出妥协:

  • 选择 3-5 个预定义的不透明度级别
  • 根据预定义的不透明度级别绘制星星数量
  • 在循环之前使用单个 beginPath()
  • 使用 rect() 而不是 moveTo+LineTo
  • 在循环结束后填充一次,继续下一个不透明度级别
  • 获取 3D 上下文一次,而不是每次调用
  • 对星星位置使用整数值(强制整数步长,在这种情况下可能不理想但值得一试)
  • 回收/重复使用明星条目,而不是拼接和创建新条目
  • 尽可能减少计算和条件的数量
  • 将帧速率降低到 30 FPS(切换 RAF 以每隔一次绘制一次)。 60 FPS 很好,但我们也可以像电影一样使用 30 FPS(不过,它们受益于运动模糊;我们可以通过使用嵌入“运动模糊”的 sprite 而不是绘制矩形来作弊)。
  • 可选:将每个字段层存储为单独的 Canvas ,绘制为图像(速度更快,但需要更多内存)。可以通过平铺旋转和/或翻转的 Canvas 来进行变化。

我个人会硬编码不透明度级别,但我将调整后的代码留在下面随机生成(多次点击运行按钮)。

通常您仍然会在 CPU/GPU 上受到一些影响,但这些技巧应该会提高性能(或者在这种情况下会减少负载)。

var NO_OF_STARS = 500; // divisable by 5 (due to num. of opacities - see below)
var stars = [];

function startGame() {
  gameArea.start(); /* Makes the canvas */

  gameRun = requestAnimationFrame(gameArea.update); /* Starts the game and coordinates all the animations */

  window.addEventListener("keydown", function(e) {
    if (e.keyCode == 27) { /* ESC stops everything */
      stopEverything();
    }
  });
}

var gameArea = {
  canvas: document.createElement("canvas"),
  ctx: null,
  opacities: [],
  start: function() {
    document.body.appendChild(this.canvas);

    this.canvas.width = document.body.clientWidth;
    this.canvas.height = document.body.clientHeight;

    // store context once
    this.ctx = this.canvas.getContext("2d");
    
    // opacity levels
    for (var t = 0; t < 5; t++) this.opacities.push(((Math.floor(Math.random() * 10) / 10) + .1));

  },
  update: function() {
    gameArea.clear(); /* Fills the canvas with #000000 */
    gameArea.drawStars(); /* Draws the stars */

    gameRun = requestAnimationFrame(gameArea.update); /* Repeat the whole thing */
  },

  drawStars: function() {
    var ctx = this.ctx;

    if (!stars.length) {
      for (var i = 0; i < NO_OF_STARS; i++) {
        stars.push({
          x: getRandomInt(0, gameArea.canvas.width - 1)|0, 
          y: getRandomInt(0, gameArea.canvas.height - 1)|0
        });
      }
    }

    for (t = 0, pos = 0; t < 5; t++) {
      var opacity = this.opacities[t];
      ctx.beginPath();

      for (var i = 0; i < NO_OF_STARS / 5; i++) {
        
        stars[pos].x -= opacity * opacity * 4;

        if (stars[pos].x < 0) {
          stars[pos].x = gameArea.canvas.width;
          stars[pos].y = getRandomInt(0, gameArea.canvas.height - 1)|0;
        }

        ctx.rect(stars[pos].x, stars[pos].y, 1, 1);
        pos++;  // total position
      }

      ctx.strokeStyle = "rgba(255, 255, 255, " + opacity + ")";
      ctx.stroke();
    }
  },
  clear: function() {
    var ctx = this.canvas.getContext("2d");
    ctx.fillStyle = "#000000";
    ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
  }
};

/**
 * Returns a random integer between min (inclusive) and max (inclusive)
 */
function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

function stopEverything() {
  cancelAnimationFrame(gameRun);
}

startGame();
* {
  margin: 0;
  padding: 0;
}
body {
  width: 100%;
  height: 100%;
}

关于javascript - Starfield canvas程序占用CPU过多,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37314595/

相关文章:

html - 带有背景图像的倾斜 div 上的文本

html - 如何动态选择 Angular 5的下拉列表?

针对移动设备优化的 Javascript Canvas 库

javascript - 像 Jasmine 一样测试 Angular Controller

javascript - 使用 javascript、php 和 json 在 html 中动态选择标签

javascript - 在 Parse Cloud Code 中按角色限制 url 访问

javascript - 是否可以绑定(bind)在 VueJS 中使用 url() 的 SVG 填充属性?

javascript - 将多个图像从工具栏拖放到 Canvas 上

ruby-on-rails - 使用 Rspec/Capybara/Selenium 与 Canvas 元素交互

javascript - 在父 div 溢出时,子 div 在父 div 之外不可见