canvas sprite sheet error - javascript

I found a lovely code snippet on canvas spritesheet animations. this is ts:
http://jsfiddle.net/m1erickson/h85Gq/
I tried to beautify this code, by writing an animate function that accepts Image objects, so that I can animate multiple images in my canvas simultaneously. This is my attempt at it:
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var spritePosition=0;
var spriteWidth=100;
var spriteHeight=100;
var spriteCount=40;
var spritePlayCount=0;
var maxSpritePlays=2;
var objectS=new Image();
objectS.src="sprites/first.png";
var fps = 50;
function animate(sprite) {
setTimeout(function() {
if(spritePlayCount<maxSpritePlays){
requestAnimationFrame(animate);
}
// Drawing code goes here
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(sprite,spritePosition*spriteWidth, 0,spriteWidth, spriteHeight, 0,0,spriteWidth, spriteHeight);
spritePosition++;
if(spritePosition>spriteCount-1){
spritePosition=0;
spritePlayCount++;
}
}, 1000 / fps);
}
objectS.onload=function(){
animate(objectS);
}
}); // end $(function(){});
I am getting a much observed error, but I cant seem to find the fix for it:
index3.html:59 Uncaught TypeError: Failed to execute 'drawImage' on
'CanvasRenderingContext2D': The provided value is not of type
'(CSSImageValue or HTMLImageElement or HTMLVideoElement or
HTMLCanvasElement or ImageBitmap or OffscreenCanvas)'
Can you help me out finding my bug?

OMDG!
quote OP "Imagine having 50 spritesheets you want to animate."
50!
Looking at the code "accepted answer version"
function animate(sprite) {
// create a timer event to fire in 1/50th second
setTimeout(function() {
if (spritePlayCount < maxSpritePlays) {
// create a animation frame event that may fire at some time
// between 0 and 16ms or 32ms from now
requestAnimationFrame(function() {
animate(sprite);
});
}
// Drawing code etc... Make canvas dirty
// exiting with dirty canvas outside the requestAnimationFrame
// forces DOM to swap canvas backbuffer immediately on exit.
}, 1000 / 50);
}
This is the worst possible way to animate one, let alone more than one sprite.
The timing is out of sync with the display refresh.
Using requestAnimationFrame's callback to create a timed event that renders, completely negates the reason for using requestAnimationFrame. requestAnimationFrame tells the DOM that what you draw in the callback is part of an animation. Using a timer to draw means you don't draw anything in the requested frame making the request redundant.
requestAnimationFrame does its best to get all the callbacks in before the next display refresh (1/60th) but it will delay the callback if there is no time to update the DOM (swap all dirty buffers) before the next refresh. You have no control over the timing of your animation, they may fire anywhere from 20ms to 36ms or more with no consistency over time.
By using timers to draw to the canvas you are forcing a backbuffer swap for each sprite you draw. This is an expensive process and will severely limit the speed that you can draw sprites at, and cause sprites to flicker and shear randomly during animation.
The best practice way.
Use a single animation loop triggered by requestAnimationFrame.
Use an array to store all sprites and update them in the main loop if/as needed.
Only render from within the main loop. Do not render or do anything (at a regular interval) to the DOM outside the main loop's execution.
Don't render inside events like timers, IO, or any other rapidly firing event.

You'll also need to pass the sprite parameter when calling the animate function using requestAnimationFrame.
$(function() {
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var spritePosition = 0;
var spriteWidth = 100;
var spriteHeight = 100;
var spriteCount = 40;
var spritePlayCount = 0;
var maxSpritePlays = 2;
var objectS = new Image();
objectS.src = "sprites/first.png";
var fps = 50;
function animate(sprite) {
setTimeout(function() {
if (spritePlayCount < maxSpritePlays) {
requestAnimationFrame(function() {
animate(sprite);
});
}
// Drawing code goes here
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(sprite, spritePosition * spriteWidth, 0, spriteWidth, spriteHeight, 0, 0, spriteWidth, spriteHeight);
spritePosition++;
if (spritePosition > spriteCount - 1) {
spritePosition = 0;
spritePlayCount++;
}
}, 1000 / fps);
}
objectS.onload = function() {
animate(objectS);
};
});

Related

Smooth Animation in HTML5 Canvas [duplicate]

This question already has answers here:
get a smooth animation for a canvas game
(3 answers)
Closed 6 years ago.
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.
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.
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.

How can I write this canvas painting loop with a JS iterator?

I'm reading through an article about continuously drawing frames of an HTML5 video player on canvas. I heard about JS iterators a while back and I think there's supposedly a performance advantage, so I'd like to know how to write this set of canvas painting functions into an Iterator:
function canvasVideo(){
//DECLARE VARIABLES
var $videoPlayer = $('#Video-Player');
var $canvasPlayer = $('#Canvas-Player');
var videoPlayer = $videoPlayer[0];
var canvasPlayer = $canvasPlayer[0];
var context = canvasPlayer.getContext("2d");
//DEFINE FUNCTIONS
//draw the video
function draw() {
//Background
context.fillStyle = '#ffffaa';
context.fillRect(0, 0, canvasPlayer.width, canvasPlayer.height);
//video
context.drawImage(videoPlayer , 0, 0);
}
//update canvas at set interval with video frames
function canvasRun(int){
//set timer with interval
var canvasTimer = setTimeout(canvasRun, int);
//draw the video frame
draw();
}
function videoLoaded(event){
canvasRun(20);
}
//HANDLE EVENTS
$videoPlayer.on("canplaythrough", function (){
videoLoaded();
});
}
How can this be done with JS Iterators?
I've tried using tutorials to learn iterators, but the generic examples have confused me, and think seeing it done with an example like this would help me learn.

Qt QML Canvas requestPaint does not repaint immediately the scene

I'm trying to adapt the Html JS library Char.js to QML QtQuick 2.4.
The library have a function to animate the scene. Everything works great if I don't animate it (eg animate with only 1 step of animation).
When the animation is used, the canvas freeze until the animation is finished, and then the scene is redraw. Here is the animationLoop function:
animationLoop = helpers.animationLoop = function(callback,totalSteps,easingString,onProgress,onComplete,chartInstance){
var currentStep = 0,
easingFunction = easingEffects[easingString] || easingEffects.linear;
var animationFrame = function(){
currentStep++;
var stepDecimal = currentStep/totalSteps;
var easeDecimal = easingFunction(stepDecimal);
callback.call(chartInstance,easeDecimal,stepDecimal, currentStep);
onProgress.call(chartInstance,easeDecimal,stepDecimal);
if (currentStep < totalSteps){
chartInstance.animationFrame = chartInstance.chart.canvas.requestAnimationFrame(animationFrame);
} else{
onComplete.apply(chartInstance);
}
};
chartInstance.chart.canvas.requestAnimationFrame(animationFrame);
},
Each time the onAnimationProgress callback is called, I call the Canvas requestPaint function to redraw the scene. Unfortunately, the onPaint function will not be called until every step of the animation have finished.
The Canvas object is accessible with : chartInstance.chart.canvas. I tried to call directely chartInstance.chart.canvas.requestPaint() at each iteration too, which do not work. I still have to wait until the animation finished to see the scene redraw correctly.
onAnimationProgress: function(easeDecimal,stepDecimal) {
skipNextChartUpdate = true;
requestPaint();
}
onPaint: {
if (!chartInstance) {
initializeChart()
} else if(!skipNextChartUpdate) {
chartInstance.scale.update({width:width,height:height});
chartInstance.resize(chartInstance.update);
} else {
skipNextChartUpdate = false;
}
}
This is more or less as I would expect. requestPaint is not an immediate operation, it's a request to repaint at a later point. So if you call requestPaint multiple times in a single JS function, you will only receive one onPaint event per (vsync) frame.
So if you want to drive an animation, you should be driving them inside onPaint, by calling requestPaint (to ask for another onPaint event in the future) if there is still more animation to show for instance.

Canvas pre rendering?

Is there any point in pre-rendering images on canvas?
An example,
var img; // Img object
var pre = document.createElement("canvas");
pre.width = img.width;
pre.height = img.height;
var precon = pre.getContext("2d");
precon.drawImage(img, 0, 0);
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
for(var i =0; i < 10000; ++i) {
ctx.drawImage(pre, Math.random() * canvas.width, Math.random() * canvas.height);
}
I don't see the point as you are still calling context.drawImage no matter what you do, unless the canvas api is faster drawing an image from a canvas object rather than image object?
Firstly, I must say that your example is not suitable to highlight the need and benefits of canvas pre-rendering.
I'll give you a better example were you need to draw multiple times something that requires heavy computation on a canvas.
Let's say you have this draw function :
function complexDraw(ctx){
ctx.drawImage(img, width, height);
// heavy computation goes here
// some transforms maybe
ctx.ctx.setTransform(-1, 0, 0, 1, 200, 200);
ctx.fillStyle = "rgba(100, 100, 255, 0.5)";
ctx.fillRect(50, 50, 100, 100);
//maybe draw another img/video/canvas
ctx.drawImage(anotherImg, width, height);
// ...
}
function draw(){
complexDraw(ctx);
}
Now let's say you want to show the current time on the canvas too. That means that we're going to add this at the bottom of our draw function :
function drawTime(ctx){
ctx.fillText(new Date().getTime(), 10, 50);
}
And now our draw function looks like this :
function draw(){
complexDraw(ctx);
drawTime(ctx);
}
Since you want to always show the current time, you need to call the draw function every second :
setInterval(draw, 1000);
This actually means that every second you are doing some heavy computation just to update a silly little text.
If only there could be a way to split the draw function and compute only the things that need computing (the ones that change)... but there is: say hello to canvas pre-rendering!
The key idea is to draw the part that doesn't change (and doesn't need to be re-computed) on a separate canvas - let's call it cacheCanvas - and just copy it's content on our app's canvas whenever we want to redraw stuff :
// suppose we have a `clone` function
var cacheCanvas = clone(canvas),
cacheCtx = cacheCanvas.getContext('2d');
// let's draw our complex stuff on the cacheCanvas
complexDraw(cacheCtx);
// modify our main `draw` function to copy the result of the `complexDraw`
// function, not to call it
function draw(){
ctx.drawImage(cacheCanvas, width, height);
drawTime();
}
And now we're basically redrawing the whole canvas each second, but we're not re-computing all the heavy work in complexDraw.
I just want to note that most of the canvas based games can't run at 60fps (redraw 60 times per second) without doing some performance boost with pre rendering or another technique called canvas layering (which is also worth looking into).

using clearRect in requestAnimationFrame does not show the animation

What I am trying to do a simple javascript animation on the HTML5 canvas. Right now my canvases are layered so that when I receive a mouse event the background layer doesn't change but the top layer with the avatars move around. If I use requestAnimationFrame and don't clear the screen, I see my nice little player moving across the screen in multiple frames with a long tail of the character. However, if I try and do the clearRect after each animation frame, then my character never appears and I'm not sure what is causing this.
I am using these links as a basis for my code:
http://www.html5canvastutorials.com/advanced/html5-canvas-start-and-stop-an-animation/
http://paulirish.com/2011/requestanimationframe-for-smart-animating/
http://www.nczonline.net/blog/2011/05/03/better-javascript-animations-with-requestanimationframe/
A lot of the examples are animating shapes that are drawn, whereas I'm using images, not sure if this matters and whether I should've just used a canvas transform function instead of clearRect, but didn't think this should've made a difference. Also, I deleted a bunch of code for readability, so the brackets may be off, but the code is functioning, I just did it for readability so you see animation in one direction. My code looks something like:
// what is this function for? See here - http://stackoverflow.com/questions/10237471/please-explain-this-requestanimationframe-idiom
window.requestAnimFrame = function(callback){
// add in this parentheses - http://stackoverflow.com/questions/5605588/how-to-use- requestanimationframe
return ( window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback){
window.setTimeout(callback, 1000 / 60);
}
);
}();
function stopAnimatingPlayer(currentAvatarAnimating, destinationCellX, destinationCellY) {
gIsAnimating = false;
//Did this final draw because I wasn't sure if the avatar would end on the exact pixel position, so this should snap him back into place
drawAvatar(currentAvatarAnimating, destinationCellX, destinationCellY, false,0,0);
}
function movePlayer(lastTime, playerPixelX, playerPixelY, destinationCellX, destinationCellY) {
if (gIsAnimating) {
// the canvas is already globally held as gAvatarCanvasElement & gAvatarDrawingContext;
// update
var date = new Date();
var time = date.getTime();
var timeDiff = time - lastTime;
var linearSpeed = 100;
// pixels / second
var linearDistEachFrame = linearSpeed * timeDiff / 1000;
var horizontal = false;
var newX, newY;
// gets the new coordinate of the player
if (gTowerCurrentPlayer == 1) {
//fill in later - just trying to get one horizontal animation working
} else if (destinationCellY == gPlayer1Cell.y) { // we're moving horizontally
var currentX = playerPixelX;
var diffX = destinationCellX - gPlayer1Cell.x;
horizontal = true;
if (diffX > 0) { // player is moving right - just get one direction going for now
if (currentX < getPixelFromRow(destinationCellX)) {
newX = currentX + linearDistEachFrame;
} else {
stopAnimatingPlayer(gTowerCurrentPlayer, destinationCellX, destinationCellY);
}
} //fill in rest later - get one direction working
lastTime = time;
// clear - this is where the problem is
gAvatarDrawingContext.clearRect(playerPixelX, playerPixelY, kPieceWidth, kPieceHeight);
//gAvatarDrawingContext.clearRect(0,0, gAvatarCanvasElement.width, gAvatarCanvasElement.height);
if (horizontal) {
drawAvatar(gTowerCurrentPlayer, 0, 0, true, newX, playerPixelY);
// request new frame
requestAnimFrame(function(){
movePlayer(lastTime, newX, playerPixelY, destinationCellX, destinationCellY);
});
}
}
}
function animatePlayer(playerPixelX, playerPixelY, destinationCellX, destinationCellY) {
gIsAnimating = true; // global var here
var date = new Date();
var time = date.getTime();
movePlayer(time, playerPixelX, playerPixelY, destinationCellX, destinationCellY);
}
If anyone could provide any help I would really appreciate it, I'm just not getting why this is not working. I don't need super flashy animations which is why i didn't go with kineticjs or any of the other libraries out there.
Thanks.
When you clear the canvas, it erases everything on it, so if you call it after you've drawn you get a blank canvas, which is what you are describing. Instead, you should wait until the next frame, and then clear the canvas before drawing, rather than after, so that the drawn image shows up for a while. To fix your problem, just move your clearing command up so that it happens immediately before your drawing commands for each frame.

Categories

Resources