window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oR
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.
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);
}
});