如何提高 Html5 Canvas 性能
How to Improve Html5 Canvas Performance
所以我一直在做这个项目,它的目标是在二维平面上随机生成地形,并在背景中放置雨水,我选择使用 html5 canvas 元素来实现这个目标。创建它后,我对结果很满意,但我遇到了性能问题,可以使用一些关于如何修复它的建议。到目前为止,我只尝试清除所需的 canvas 位,它位于我在地形下绘制的矩形上方以填充它,但因此我必须重新绘制圆圈。 rn(rain number) 已经降低了大约 2 倍,而且仍然滞后,有什么建议吗?
注意 - 代码片段中的代码不会因为它的体积小而滞后,但如果我要 运行 全屏显示实际降雨量 (800),它会滞后。我有 sh运行k 适合代码段的值。
var canvas = document.getElementById('gamecanvas');
var c = canvas.getContext('2d');
var ma = Math.random;
var mo = Math.round;
var wind = 5;
var rn = 100;
var rp = [];
var tp = [];
var tn;
function setup() {
//fillstyle
c.fillStyle = 'black';
//canvas size
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
//rain setup
for (i = 0; i < rn; i++) {
let x = mo(ma() * canvas.width);
let y = mo(ma() * canvas.width);
let w = mo(ma() * 1) + 1;
let s = mo(ma() * 5) + 10;
rp[i] = { x, y, w, s };
}
//terrain setup
tn = (canvas.width) + 20;
tp[0] = { x: -2, y: canvas.height - 50 };
for (i = 1; i <= tn; i++) {
let x = tp[i - 1].x + 2;
let y = tp[i - 1].y + (ma() * 20) - 10;
if (y > canvas.height - 50) {
y = tp[i - 1].y -= 1;
}
if (y < canvas.height - 100) {
y = tp[i - 1].y += 1;
}
tp[i] = { x, y };
c.fillRect(x, y, 4, canvas.height - y);
}
}
function gameloop() {
//clearing canvas
for (i = 0; i < tn; i++) {
c.clearRect(tp[i].x - 2, 0, 2, tp[i].y);
}
for (i = 0; i < rn; i++) {
//rain looping
if (rp[i].y > canvas.height + 5) {
rp[i].y = -5;
}
if (rp[i].x > canvas.width + 5) {
rp[i].x = -5;
}
//rain movement
rp[i].y += rp[i].s;
rp[i].x += wind;
//rain drawing
c.fillRect(rp[i].x, rp[i].y, rp[i].w, 6);
}
for (i = 0; i < tn; i++) {
//terrain drawing
c.beginPath();
c.arc(tp[i].x, tp[i].y, 6, 0, 7);
c.fill();
}
}
setup();
setInterval(gameloop, 1000 / 60);
body {
background-color: white;
overflow: hidden;
margin: 0;
}
canvas {
background-color: white;
}
<html>
<head>
<link rel="stylesheet" href="index.css">
<title>A Snowy Night</title>
</head>
<body id="body"> <canvas id="gamecanvas"></canvas>
<script src="index.js"></script>
</body>
</html>
一般来说,拥有更多的绘画指令成本最高,这些绘画指令的复杂性只有在真的复杂时才会发挥作用。
你在这里用绘画指令向 GPU 发送垃圾邮件:
(canvas.width) + 20
调用 clearRect()
. clearRect()
是 绘画指令,而不是一个便宜的。偶尔使用它,但实际上,您应该只使用它来清除整个上下文。
- 每个雨滴
fillRect()
。。它们都是相同的颜色,它们可以合并到单个子路径中并在单个绘制调用中绘制。
- 构成地形的每个圆填充一个。
因此,我们可以只用两次绘制调用来代替大量的绘制调用:
一个 clearRect
,一个 fill()
一个包含 drops 和
地形。
然而,将地形和雨水分开肯定更实用,所以让我们通过将地形保留在其自己的 Path2D 对象中来进行三个绘制调用,这对 CPU 更友好:
var canvas = document.getElementById('gamecanvas');
var c = canvas.getContext('2d');
var ma = Math.random;
var mo = Math.round;
var wind = 5;
var rn = 100;
var rp = [];
// this will hold our Path2D object
// which will hold the full terrain drawing
// set a 'let' because we will set it again on resize
let terrain;
var tp = [];
var tn;
function setup() {
//fillstyle
c.fillStyle = 'black';
//canvas size
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
//rain setup
for (let i = 0; i < rn; i++) {
let x = mo(ma() * canvas.width);
let y = mo(ma() * canvas.width);
let w = mo(ma() * 1) + 1;
let s = mo(ma() * 5) + 10;
rp[i] = { x, y, w, s };
}
//terrain setup
tn = (canvas.width) + 20;
tp[0] = { x: -2, y: canvas.height - 50 };
terrain = new Path2D();
for (let i = 1; i <= tn; i++) {
let x = tp[i - 1].x + 2;
let y = tp[i - 1].y + (ma() * 20) - 10;
if (y > canvas.height - 50) {
y = tp[i - 1].y -= 1;
}
if (y < canvas.height - 100) {
y = tp[i - 1].y += 1;
}
tp[i] = { x, y };
terrain.rect(x, y, 4, canvas.height - y);
terrain.arc(x, y, 6, 0, Math.PI*2);
}
}
function gameloop() {
// clear the whole canvas
c.clearRect(0, 0, canvas.width, canvas.height);
// start a new sub-path for the rain
c.beginPath();
for (let i = 0; i < rn; i++) {
//rain looping
if (rp[i].y > canvas.height + 5) {
rp[i].y = -5;
}
if (rp[i].x > canvas.width + 5) {
rp[i].x = -5;
}
//rain movement
rp[i].y += rp[i].s;
rp[i].x += wind;
//rain tracing
c.rect(rp[i].x, rp[i].y, rp[i].w, 6);
}
// paint all the drops in a single op
c.fill();
// paint the whole terrain in a single op
c.fill(terrain);
// loop at screen refresh frequency
requestAnimationFrame(gameloop);
}
setup();
requestAnimationFrame(gameloop);
onresize = () => setup();
body {
background-color: white;
overflow: hidden;
margin: 0;
}
canvas {
background-color: white;
}
<canvas id="gamecanvas"></canvas>
进一步可能的改进:
不要让我们的地形路径成为一组矩形,只使用 lineTo
来描绘实际轮廓可能会有所帮助,在初始化时进行更多计算,但它只完成一次一会儿。
如果地形变得更复杂,有更多细节,或者有各种颜色和阴影等,那么考虑只绘制一次,然后从 canvas 生成一个 ImageBitmap。然后在 gameLoop
你只需要 drawImage
ImageBitmap(绘制位图非常快,但是存储它会消耗内存,所以记得 .close()
ImageBitmap 当你不需要它时不再)。
叠加canvas
就像我在评论中建议的那样,使用第二个 canvas 点只需要绘制一次地形,因此它可以通过在每次新绘制时保存重绘来提高动画的性能框架。这可以通过 CSS 将一个放在另一个上(如图层)来完成。
#canvasBase {
position: relative;
}
#canvasLayer1 {
position: absolute;
top: 0;
left: 0;
}
#canvasLayer2 {
position: absolute;
top: 0;
left: 0;
}
// etc...
另外我建议你使用 requestAnimationFrame over setinterval ().
requestAnimationFrame
但是,通过使用 requestAnimationFrame
,我们无法控制刷新率,它与客户端硬件相关联。所以我们需要处理它,为此,我们将使用 DOMHighResTimeStamp
作为参数传递给我们的回调方法。
我们的想法是让它以本机速度 运行 并通过仅在需要的时间更新逻辑(我们的计算)来管理 fps。例如,如果我们需要 fps = 60;
,这意味着我们需要每隔 1000 / 60 = ~16,67 ms
更新我们的逻辑。因此,我们检查最后一帧时间的 deltaTime 是否等于或优于 ~16,67ms。如果没有足够的时间过去,我们调用一个新的框架,我们 return (重要的是,否则我们刚刚做的控制是无用的,因为无论结果如何,代码都会继续运行) .
let fps = 60;
/* Check if we need to update the logic */
/* if not request a new frame & return */
if(deltaLastUpdate <= 1000 / fps){ // 1000 / 60 = ~16,67ms
requestAnimationFrame(animate);
return;
}
正在清算canvas
因为你需要擦除所有过去的雨滴,所以最简单和最便宜的资源是一举清除整个上下文。
ctxRain.clearRect(0, 0, rainCanvas.width, rainCanvas.height);
2D 路径
由于您的绘图使用相同颜色的雨滴,您也可以将所有这些都分组到一个路径中:
rainPath = new Path2D();
...
所以你只需要一条指令来绘制它们(与 clearRect 相同的资源保存类型):
ctxRain.fill(rainPath);
结果
/* CANVAS "Terrain" */
const terrainCanvas = document.getElementById('gameTerrain');
const ctxTerrain = terrainCanvas.getContext('2d');
terrainCanvas.height = window.innerHeight;
terrainCanvas.width = window.innerWidth;
/* CANVAS "Rain" */
const rainCanvas = document.getElementById('gameRain');
const ctxRain = rainCanvas.getContext('2d');
rainCanvas.height = window.innerHeight;
rainCanvas.width = window.innerWidth;
/* Game Constants */
const wind = 5;
const rainMaxParticules = 100;
const rain = [];
let rainPath;
const terrainMaxParticules = terrainCanvas.width + 20;
const terrain = [];
let terrainPath;
/* Maths help */
const ma = Math.random;
const mo = Math.round;
/* Clear */
function clearTerrain(){
ctxTerrain.clearRect(0, 0, terrainCanvas.width, terrainCanvas.height);
}
function clearRain(){
ctxRain.clearRect(0, 0, rainCanvas.width, rainCanvas.height);
}
/* Logic */
function initTerrain(){
terrain[0] = { x: -2, y: terrainCanvas.height - 50 };
for (let i = 1; i <= terrainMaxParticules; i++) {
let x = terrain[i - 1].x + 2;
let y = terrain[i - 1].y + (ma() * 20) - 10;
if (y > terrainCanvas.height - 50) {
y = terrain[i - 1].y -= 1;
}
if (y < terrainCanvas.height - 100) {
y = terrain[i - 1].y += 1;
}
terrain[i] = { x, y };
}
}
function initRain(){
for (let i = 0; i < rainMaxParticules; i++) {
let x = mo(ma() * rainCanvas.width);
let y = mo(ma() * rainCanvas.width);
let w = mo(ma() * 1) + 1;
let s = mo(ma() * 5) + 10;
rain[i] = { x, y, w, s };
}
}
function init(){
initTerrain();
initRain();
}
function updateTerrain(){
terrainPath = new Path2D();
for(let i = 0; i < terrain.length; i++){
terrainPath.arc(terrain[i].x, terrain[i].y, 6, Math.PI/2, 5*Math.PI/2);
}
terrainPath.lineTo(terrainCanvas.width, terrainCanvas.height);
terrainPath.lineTo(0, terrainCanvas.height);
}
function updateRain(){
rainPath = new Path2D();
for (let i = 0; i < rain.length; i++) {
// Rain looping
if (rain[i].y > rainCanvas.height + 5) {
rain[i].y = -5;
}
if (rain[i].x > rainCanvas.width + 5) {
rain[i].x = -5;
}
// Rain movement
rain[i].y += rain[i].s;
rain[i].x += wind;
// Path containing all the drops
rainPath.rect(rain[i].x, rain[i].y, rain[i].w, 6);
}
}
/* Drawing */
function drawTerrain(){
ctxTerrain.fillStyle = 'black';
ctxTerrain.fill(terrainPath);
}
function drawRain(){
ctxRain.fillStyle = 'black';
ctxRain.fill(rainPath);
}
/* Animation Constant */
const fps = 60;
let lastTimestampUpdate;
let terrainDrawn = false;
/* Game loop */
function animate(timestamp){
/* Initialize rain & terrain particules */
if(rain.length === 0 || terrain.length === 0){
init();
}
/* Define "lastTimestampUpdate" from the first call */
if (lastTimestampUpdate === undefined){
lastTimestampUpdate = timestamp;
}
/* Check if we need to update the logic & the drawing, if not, request a new frame & return */
if(timestamp - lastTimestampUpdate <= 1000 / fps){
requestAnimationFrame(animate);
return;
}
if(!terrainDrawn){
/* Terrain --------------------- */
/* Clear */
clearTerrain();
/* Logic */
updateTerrain();
/* Draw */
drawTerrain();
/* ----------------------------- */
terrainDrawn = true;
}
/* --- Rain -------------------- */
/* Clear */
clearRain();
/* Logic */
updateRain();
/* Draw */
drawRain();
/* ----------------------------- */
/* Request another frame */
lastTimestampUpdate = timestamp;
requestAnimationFrame(animate);
}
/* Start the animation */
requestAnimationFrame(animate);
body {
background-color: white;
overflow: hidden;
margin: 0;
}
#gameTerrain {
position: relative;
}
#gameRain {
position: absolute;
top: 0;
left: 0;
}
<body>
<canvas id="gameTerrain"></canvas>
<canvas id="gameRain"></canvas>
</body>
一边
这不会影响性能,但是我鼓励您使用 const & let over var (What's the difference between using “let” and “var”?)。
所以我一直在做这个项目,它的目标是在二维平面上随机生成地形,并在背景中放置雨水,我选择使用 html5 canvas 元素来实现这个目标。创建它后,我对结果很满意,但我遇到了性能问题,可以使用一些关于如何修复它的建议。到目前为止,我只尝试清除所需的 canvas 位,它位于我在地形下绘制的矩形上方以填充它,但因此我必须重新绘制圆圈。 rn(rain number) 已经降低了大约 2 倍,而且仍然滞后,有什么建议吗?
注意 - 代码片段中的代码不会因为它的体积小而滞后,但如果我要 运行 全屏显示实际降雨量 (800),它会滞后。我有 sh运行k 适合代码段的值。
var canvas = document.getElementById('gamecanvas');
var c = canvas.getContext('2d');
var ma = Math.random;
var mo = Math.round;
var wind = 5;
var rn = 100;
var rp = [];
var tp = [];
var tn;
function setup() {
//fillstyle
c.fillStyle = 'black';
//canvas size
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
//rain setup
for (i = 0; i < rn; i++) {
let x = mo(ma() * canvas.width);
let y = mo(ma() * canvas.width);
let w = mo(ma() * 1) + 1;
let s = mo(ma() * 5) + 10;
rp[i] = { x, y, w, s };
}
//terrain setup
tn = (canvas.width) + 20;
tp[0] = { x: -2, y: canvas.height - 50 };
for (i = 1; i <= tn; i++) {
let x = tp[i - 1].x + 2;
let y = tp[i - 1].y + (ma() * 20) - 10;
if (y > canvas.height - 50) {
y = tp[i - 1].y -= 1;
}
if (y < canvas.height - 100) {
y = tp[i - 1].y += 1;
}
tp[i] = { x, y };
c.fillRect(x, y, 4, canvas.height - y);
}
}
function gameloop() {
//clearing canvas
for (i = 0; i < tn; i++) {
c.clearRect(tp[i].x - 2, 0, 2, tp[i].y);
}
for (i = 0; i < rn; i++) {
//rain looping
if (rp[i].y > canvas.height + 5) {
rp[i].y = -5;
}
if (rp[i].x > canvas.width + 5) {
rp[i].x = -5;
}
//rain movement
rp[i].y += rp[i].s;
rp[i].x += wind;
//rain drawing
c.fillRect(rp[i].x, rp[i].y, rp[i].w, 6);
}
for (i = 0; i < tn; i++) {
//terrain drawing
c.beginPath();
c.arc(tp[i].x, tp[i].y, 6, 0, 7);
c.fill();
}
}
setup();
setInterval(gameloop, 1000 / 60);
body {
background-color: white;
overflow: hidden;
margin: 0;
}
canvas {
background-color: white;
}
<html>
<head>
<link rel="stylesheet" href="index.css">
<title>A Snowy Night</title>
</head>
<body id="body"> <canvas id="gamecanvas"></canvas>
<script src="index.js"></script>
</body>
</html>
一般来说,拥有更多的绘画指令成本最高,这些绘画指令的复杂性只有在真的复杂时才会发挥作用。
你在这里用绘画指令向 GPU 发送垃圾邮件:
(canvas.width) + 20
调用clearRect()
.clearRect()
是 绘画指令,而不是一个便宜的。偶尔使用它,但实际上,您应该只使用它来清除整个上下文。- 每个雨滴
fillRect()
。。它们都是相同的颜色,它们可以合并到单个子路径中并在单个绘制调用中绘制。 - 构成地形的每个圆填充一个。
因此,我们可以只用两次绘制调用来代替大量的绘制调用:
一个 clearRect
,一个 fill()
一个包含 drops 和
地形。
然而,将地形和雨水分开肯定更实用,所以让我们通过将地形保留在其自己的 Path2D 对象中来进行三个绘制调用,这对 CPU 更友好:
var canvas = document.getElementById('gamecanvas');
var c = canvas.getContext('2d');
var ma = Math.random;
var mo = Math.round;
var wind = 5;
var rn = 100;
var rp = [];
// this will hold our Path2D object
// which will hold the full terrain drawing
// set a 'let' because we will set it again on resize
let terrain;
var tp = [];
var tn;
function setup() {
//fillstyle
c.fillStyle = 'black';
//canvas size
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
//rain setup
for (let i = 0; i < rn; i++) {
let x = mo(ma() * canvas.width);
let y = mo(ma() * canvas.width);
let w = mo(ma() * 1) + 1;
let s = mo(ma() * 5) + 10;
rp[i] = { x, y, w, s };
}
//terrain setup
tn = (canvas.width) + 20;
tp[0] = { x: -2, y: canvas.height - 50 };
terrain = new Path2D();
for (let i = 1; i <= tn; i++) {
let x = tp[i - 1].x + 2;
let y = tp[i - 1].y + (ma() * 20) - 10;
if (y > canvas.height - 50) {
y = tp[i - 1].y -= 1;
}
if (y < canvas.height - 100) {
y = tp[i - 1].y += 1;
}
tp[i] = { x, y };
terrain.rect(x, y, 4, canvas.height - y);
terrain.arc(x, y, 6, 0, Math.PI*2);
}
}
function gameloop() {
// clear the whole canvas
c.clearRect(0, 0, canvas.width, canvas.height);
// start a new sub-path for the rain
c.beginPath();
for (let i = 0; i < rn; i++) {
//rain looping
if (rp[i].y > canvas.height + 5) {
rp[i].y = -5;
}
if (rp[i].x > canvas.width + 5) {
rp[i].x = -5;
}
//rain movement
rp[i].y += rp[i].s;
rp[i].x += wind;
//rain tracing
c.rect(rp[i].x, rp[i].y, rp[i].w, 6);
}
// paint all the drops in a single op
c.fill();
// paint the whole terrain in a single op
c.fill(terrain);
// loop at screen refresh frequency
requestAnimationFrame(gameloop);
}
setup();
requestAnimationFrame(gameloop);
onresize = () => setup();
body {
background-color: white;
overflow: hidden;
margin: 0;
}
canvas {
background-color: white;
}
<canvas id="gamecanvas"></canvas>
进一步可能的改进:
不要让我们的地形路径成为一组矩形,只使用
lineTo
来描绘实际轮廓可能会有所帮助,在初始化时进行更多计算,但它只完成一次一会儿。如果地形变得更复杂,有更多细节,或者有各种颜色和阴影等,那么考虑只绘制一次,然后从 canvas 生成一个 ImageBitmap。然后在
gameLoop
你只需要drawImage
ImageBitmap(绘制位图非常快,但是存储它会消耗内存,所以记得.close()
ImageBitmap 当你不需要它时不再)。
叠加canvas
就像我在评论中建议的那样,使用第二个 canvas 点只需要绘制一次地形,因此它可以通过在每次新绘制时保存重绘来提高动画的性能框架。这可以通过 CSS 将一个放在另一个上(如图层)来完成。
#canvasBase {
position: relative;
}
#canvasLayer1 {
position: absolute;
top: 0;
left: 0;
}
#canvasLayer2 {
position: absolute;
top: 0;
left: 0;
}
// etc...
另外我建议你使用 requestAnimationFrame over setinterval (
requestAnimationFrame
但是,通过使用 requestAnimationFrame
,我们无法控制刷新率,它与客户端硬件相关联。所以我们需要处理它,为此,我们将使用 DOMHighResTimeStamp
作为参数传递给我们的回调方法。
我们的想法是让它以本机速度 运行 并通过仅在需要的时间更新逻辑(我们的计算)来管理 fps。例如,如果我们需要 fps = 60;
,这意味着我们需要每隔 1000 / 60 = ~16,67 ms
更新我们的逻辑。因此,我们检查最后一帧时间的 deltaTime 是否等于或优于 ~16,67ms。如果没有足够的时间过去,我们调用一个新的框架,我们 return (重要的是,否则我们刚刚做的控制是无用的,因为无论结果如何,代码都会继续运行) .
let fps = 60;
/* Check if we need to update the logic */
/* if not request a new frame & return */
if(deltaLastUpdate <= 1000 / fps){ // 1000 / 60 = ~16,67ms
requestAnimationFrame(animate);
return;
}
正在清算canvas
因为你需要擦除所有过去的雨滴,所以最简单和最便宜的资源是一举清除整个上下文。
ctxRain.clearRect(0, 0, rainCanvas.width, rainCanvas.height);
2D 路径
由于您的绘图使用相同颜色的雨滴,您也可以将所有这些都分组到一个路径中:
rainPath = new Path2D();
...
所以你只需要一条指令来绘制它们(与 clearRect 相同的资源保存类型):
ctxRain.fill(rainPath);
结果
/* CANVAS "Terrain" */
const terrainCanvas = document.getElementById('gameTerrain');
const ctxTerrain = terrainCanvas.getContext('2d');
terrainCanvas.height = window.innerHeight;
terrainCanvas.width = window.innerWidth;
/* CANVAS "Rain" */
const rainCanvas = document.getElementById('gameRain');
const ctxRain = rainCanvas.getContext('2d');
rainCanvas.height = window.innerHeight;
rainCanvas.width = window.innerWidth;
/* Game Constants */
const wind = 5;
const rainMaxParticules = 100;
const rain = [];
let rainPath;
const terrainMaxParticules = terrainCanvas.width + 20;
const terrain = [];
let terrainPath;
/* Maths help */
const ma = Math.random;
const mo = Math.round;
/* Clear */
function clearTerrain(){
ctxTerrain.clearRect(0, 0, terrainCanvas.width, terrainCanvas.height);
}
function clearRain(){
ctxRain.clearRect(0, 0, rainCanvas.width, rainCanvas.height);
}
/* Logic */
function initTerrain(){
terrain[0] = { x: -2, y: terrainCanvas.height - 50 };
for (let i = 1; i <= terrainMaxParticules; i++) {
let x = terrain[i - 1].x + 2;
let y = terrain[i - 1].y + (ma() * 20) - 10;
if (y > terrainCanvas.height - 50) {
y = terrain[i - 1].y -= 1;
}
if (y < terrainCanvas.height - 100) {
y = terrain[i - 1].y += 1;
}
terrain[i] = { x, y };
}
}
function initRain(){
for (let i = 0; i < rainMaxParticules; i++) {
let x = mo(ma() * rainCanvas.width);
let y = mo(ma() * rainCanvas.width);
let w = mo(ma() * 1) + 1;
let s = mo(ma() * 5) + 10;
rain[i] = { x, y, w, s };
}
}
function init(){
initTerrain();
initRain();
}
function updateTerrain(){
terrainPath = new Path2D();
for(let i = 0; i < terrain.length; i++){
terrainPath.arc(terrain[i].x, terrain[i].y, 6, Math.PI/2, 5*Math.PI/2);
}
terrainPath.lineTo(terrainCanvas.width, terrainCanvas.height);
terrainPath.lineTo(0, terrainCanvas.height);
}
function updateRain(){
rainPath = new Path2D();
for (let i = 0; i < rain.length; i++) {
// Rain looping
if (rain[i].y > rainCanvas.height + 5) {
rain[i].y = -5;
}
if (rain[i].x > rainCanvas.width + 5) {
rain[i].x = -5;
}
// Rain movement
rain[i].y += rain[i].s;
rain[i].x += wind;
// Path containing all the drops
rainPath.rect(rain[i].x, rain[i].y, rain[i].w, 6);
}
}
/* Drawing */
function drawTerrain(){
ctxTerrain.fillStyle = 'black';
ctxTerrain.fill(terrainPath);
}
function drawRain(){
ctxRain.fillStyle = 'black';
ctxRain.fill(rainPath);
}
/* Animation Constant */
const fps = 60;
let lastTimestampUpdate;
let terrainDrawn = false;
/* Game loop */
function animate(timestamp){
/* Initialize rain & terrain particules */
if(rain.length === 0 || terrain.length === 0){
init();
}
/* Define "lastTimestampUpdate" from the first call */
if (lastTimestampUpdate === undefined){
lastTimestampUpdate = timestamp;
}
/* Check if we need to update the logic & the drawing, if not, request a new frame & return */
if(timestamp - lastTimestampUpdate <= 1000 / fps){
requestAnimationFrame(animate);
return;
}
if(!terrainDrawn){
/* Terrain --------------------- */
/* Clear */
clearTerrain();
/* Logic */
updateTerrain();
/* Draw */
drawTerrain();
/* ----------------------------- */
terrainDrawn = true;
}
/* --- Rain -------------------- */
/* Clear */
clearRain();
/* Logic */
updateRain();
/* Draw */
drawRain();
/* ----------------------------- */
/* Request another frame */
lastTimestampUpdate = timestamp;
requestAnimationFrame(animate);
}
/* Start the animation */
requestAnimationFrame(animate);
body {
background-color: white;
overflow: hidden;
margin: 0;
}
#gameTerrain {
position: relative;
}
#gameRain {
position: absolute;
top: 0;
left: 0;
}
<body>
<canvas id="gameTerrain"></canvas>
<canvas id="gameRain"></canvas>
</body>
一边
这不会影响性能,但是我鼓励您使用 const & let over var (What's the difference between using “let” and “var”?)。