Smooth Animation in HTML5 Canvas [duplicate]

2019-09-16 06:05发布

问题:

This question already has an answer here:

  • get a smooth animation for a canvas game 3 answers

So I was creating a game on the canvas in HTML and Javascript. I wanted to make some kind of flappy bird-ish game but when I press a key, the animation of the player looks really stuttering. Take a look:

body {
    overflow: hidden;
}
<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" href="Style.css"/>
</head>
<body onload="startgame()">
    <canvas id="canvas"></canvas>
<script>
    canvas.height=window.innerHeight;
    canvas.width=window.innerWidth;

function startgame() {
    var c = document.getElementById("canvas");
    var ctx = c.getContext("2d");
    
    var x = 900;
    var y = 300;
    var w = 25;
    var h = 500;
    var yperson = 20;
    var xperson = 200;
    
    document.addEventListener("keydown", function() {
        yperson -= 150;
    });
    
    function updateperson() {
        yperson = yperson;
    }
    
    setInterval(createobject, 10);
    function createobject() {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        
        x -= 1;
        yperson += 0.5;
        yperson *= 1.003;
        
        ctx.fillStyle = "#009999";
        ctx.fillRect(x,y,w,h);
        
        ctx.fillStyle = "black";
        ctx.fillRect(xperson,yperson,30,30);
        
        if (x <= 50) {
            if (yperson < 280 && xperson === x-30) {
                x -= 1;
            } else if (yperson > 280){
                x += 1;
            }
        }
      
        
    }
}
</script>
</body>
</html>

I want it to have a smooth animation up. I have seen some people saying it should be done with requestanimationframe but I don't know how to use it.

Thanks in advance.

回答1:

requestAnimationFrame

For full details see MDN window.requestAnimationFrame

As the previous answer is lacking some information, here is annotated example of the basic usage.

// A flag to indicate that the animation is over
var stop = false; // when true the animation will stop

// define main loop update
// the callback that is the main loop
// the browser treats this function as special in terms of display items including
// the canvas, and all DOM items.
// It will ensure that any changes you make to the page are synced to the display
function update(time){  // time is the time since load in millisecond 1/1000th
                        // time is high precision and gives the time down to
                        // microseconds (1/1,000,000) as fraction 0.001 is one microsecond

    // you can stop the animation by simply not calling the request
    // so if the flag stop is true stop the animation
    if(!stop){
        requestAnimationFrame(update); // request the next frame   
    }
}

requestAnimationFrame(update); // request the very first frame 
// or you can start it with a direct call. But you will have to add the time
update(0);

The update function will be called up to 60 times a second. If the code can not keep up (ie it take more than 1/60th of a second to render) then the update function will wait for the next frame effectively reducing the frame rate to 1/30. It will continue skipping frames if the render is slow.

Because you can not control the frame rate you can do the following to slow the animation down to a required frame rate.

const FRAMES_PER_SECOND = 30;  // Valid values are 60,30,20,15,10
// set the mim time to render the next frame
const FRAME_MIN_TIME = (1000/60) * (60 / FRAMES_PER_SECOND) - (1000/60) * 0.5;
var lastFrameTime = 0;  // the last frame time
function update(time){
    if(time-lastFrameTime < FRAME_MIN_TIME){ //skip the frame if the call is to early
        requestAnimationFrame(update);
        return; // return as there is nothing to do
    }
    lastFrameTime = time; // remember the time of the rendered frame
    // render the frame
    requestAnimationFrame(update);
}

If you change the focus to a another tab the browser will no longer call the request until focus is returned to the tab.

Like other timer events the call requestAnimationFrame returns a ID that can be used to cancel the callback event

var id = requestAnimationFrame(update);
// to cancel 
cancelAnimationFrame(id);

You can actually call requestAnimationFrame more than once per frame. As long as all the requests can render within the 1/60th of a second they all will by synced and presented to the display at the same time. But you must be careful because they can come out of sync if the rendering time is too long.

RequestAnimationFrame prevents flickering (displaying the canvas when the rendering is not complete) by double buffering changes. Syncs to the display hardware and prevents shearing (caused when the display is updated midway through a frame and the top half of the display shows the old frame and bottom the new frame). There are more benefits depending on the browser.



回答2:

This is how I set up my games:

 // DEFINE OBJECTS UP HERE

 var update = function(modifier) {
     // update all the object properties
     // multiply values that depend on time (like speeds) by modifier
 };
 var render = function() {
     // draw everything
 };
 var main = function() {
     var now = Date.now();
     var change = now - then;

     update(change/1000); // update based on frame rate, change in milliseconds/second
     render();
     then = now;
     requestAnimationFrame(main);
 };

 // ADD EVENT LISTENERS HERE

 requestAnimationFrame = window.requestAnimationFrame
                 || window.webkitRequestAnimationFrame
                 || window.msRequestAnimationFrame 
                 || window.mozRequestAnimationFrame;
 // ABOVE CODE GIVES CROSS-BROWSER COMPATIBILITY

  var then = Date.now();
  main();

requestAnimationFrame tells the browser to execute the loop based on the frame rate. Personally, I don't understand how it works, although if someone here did I'd be very interested in knowing more. setInterval allows you to set how quickly you want the loop to run, but the optimal rate will depend on the browser. The "then" and "now" variables are for determining how long has passed since the last execution of the loop. This value can be passed to the update function and used for calculations that depend on the frame rate, although sometimes you don't need it and can just use:

 var update = function() {
      //STUFF
 };

 // if using that variation just ignore then and now and call:
 update();
 //in your main

Although using then and now is better.