Flickering images in canvas animation

前端 未结 2 1071
说谎
说谎 2021-01-22 07:01
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oR         


        
相关标签:
2条回答
  • 2021-01-22 07:55

    Canvas animations.

    When animating on a browser always use resquestAnimationFrame, it gives far better results than setInterval and setTimeout by only moving new pixel data to the display in sync with the refresh time.

    Why the flicker

    It's complicated...

    The basic reason is that you were loading the image each time you wanted to use it. The onload will not fire until after your code has stopped running. When your code stops running the canvas content will be presented to the display and it will be empty because you just cleared it (no event will fire until you exit the draw function). Then the onload events will start to fire, each drawing the image on the canvas. When the event exits the browser thinks "ok you want to put that to the display!" and the whole canvas is again presented to the display. Because this happens much faster than the screen refresh rate you get you weird shearing, flickering, jumping effect.

    Another reason is the use of setInterval The Answer from DominicValenciana still uses setInterval and if you look carefully you will see that it is not a smooth as using requestAnimationFrame. Good animation should be smooth as silk (well as close a possible)

    Some tips below the demo.

    This answer will also help explain image loading and drawing.

    (function () {
        const oneDeg = Math.PI / 180;
        const D1 = Math.PI / 180; // again for lazy programmers
        const centerX = 400;
        const centerY = 400;
        const radius = 300;
        // create and load image
        const base_image = new Image();
        base_image.src = 'https://gxzzxb.files.wordpress.com/2014/12/16mario.png';
        // create size and add to DOM a canvas
        const c = document.createElement("canvas");
        c.width = 800;
        c.height = 800;
        document.body.appendChild(c);        
        const ctx = c.getContext("2d");  // get context
        var step = 0;  // the animation step
    
    
        function draw() {  // the main draw function
            var x,y,i,angle; 
            if(base_image.complete){  // has the image loaded
                ctx.setTransform(1, 0, 0, 1, 0, 0);  // reset transform                 
                ctx.clearRect(0, 0, c.width, c.height); 
                for (i = 0; i < Math.PI * 2; i += D1 * 10) {  // 360 in steps of 10
                    angle = step + i;  // angle in radians
                    x = centerX + Math.sin(angle) * radius;
                    y = centerY + Math.cos(angle) * radius;
                    ctx.setTransform(1, 0, 0, 1, x, y); // set translate and scale no need for save restore
                    ctx.rotate(angle);   // set angle
                    ctx.drawImage(base_image, 0, 0); //draw image
                }
                step += 1* D1; // step 1 deg per frame
            }
            requestAnimationFrame(draw);
        }
        draw();
    })();

    Angles, PI, and Trig

    Your calculation for the position of the sprite

    x = Math.sin(angle);
    y = Math.cos(angle);
    

    has zero (angle = 0) at the top of the display (the 12 oclock position). To match the same angle as used by the canvas rotate function use

    x = Math.cos(angle);
    y = Math.sin(angle);
    

    this has zero at the right of the screen (the 3oclock position)

    If you want to keep creating animations and computer graphics. Forget about 360 deg. Circles are 2PI around not 360, PI is half a turn 180, PI/2 is a quarter turn 90, PI/30 is a minute hands movement per minute, the distance around a circle is 2*PI*radius, around half a circle PI * radius. Every trig function in JS uses radians (not to be annoying, or obtuse, but because it is far simpler to use), You will always need to use PI at some stage if you work in deg, so just drop the unnecessary overhead.

    Constants and variables

    Only initialize variables once if you never intend to change it. Better yet if a variable reference never needs to be changed make it a constant const myVar = 10; if you try and change it in any way it will throw an error myVar = 10; ERROR!!!!!

    Animation where performance is everything

    When animating it is important to keep performance at maximum so you can invest more into the quality of the animation. When setting the transform it is far easier to use ctx.setTransform(scale,0,0,scale,x,y); ctx.rotate(angle); as you do not need to save and restore the full canvas state just for transforms. If you wish to get the default transform use ctx.setTransform(1,0,0,1,0,0);

    Don't support the dead

    No need for the moz,webkit prefixed requestAnimationFrame is everywhere. and as far as I know if a browser does not support requestAnimationFrame it does not support canvas, and we should not support as browser that does. Also requestAnimationFrame is not just for canvas, it should be used for any DOM javascript based animations you create.

    0 讨论(0)
  • 2021-01-22 08:04

    The problem is that a new instance of the image is being reloaded each time they are rendered in the circle. The network delay causes this flashing that you see. By loading the image first and then reusing the data I have removed the flashing.

    Here is a working version: http://codepen.io/anon/pen/JRVJRJ

    I have moved the loading of base_image out side of the make_base function and made it a global variable for reuse in the make_base function. The causes the image to be loaded once and reapplied multiple times.

    window.requestAnimFrame = (function(callback) {
            return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
            function(callback) {
              window.setTimeout(callback, 1000 / 60);
            };
          })(); 
    
    $(function() {
      var centerX = 400,
        centerY = 400,
        radius = 300;
    
      function make_base(ctx, angle) {
          ctx.save();
          var x = centerX + Math.sin(angle) * radius;
          var y = centerY + Math.cos(angle) * radius;
          ctx.translate(x, y);
          ctx.drawImage(base_image, 0, 0);
          ctx.rotate(angle);
          ctx.restore();
      }
    
      function draw(step) {
        var ctx = document.getElementById("canvas").getContext("2d");
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        var angle;
        for (var i = 0; i < 180; i += 10) {
          angle = step + (i * Math.PI / 90);
          make_base(ctx, angle);
        }
      }
    
    base_image = new Image();
        base_image.src = 'https://gxzzxb.files.wordpress.com/2014/12/16mario.png';
        base_image.onload = function(){
          var step = 0;
        draw(step);
    
        setInterval(function() {
      draw(step);
              ++step;
            }, 20);
      }
      });
    
    0 讨论(0)
提交回复
热议问题