DrawImage will not load on first frame - javascript

I have a simple draw image for my canvas but it won't display on the first frame.
It's driving me mad i dunno why it won't do it!!
This is my script:
img = new Image();
img.src = 'images/0.png'; //preLoad the image
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback, element){
window.setTimeout(callback, 1000 / 60);
};
})(); //frames per second
function gameUpdate(){
previous = 0;
requestAnimFrame( gameUpdate );
draw();
}
Then the draw() function has this:
//doesn't display on first fame
canvas['canvas1'].ctx.drawImage(img, 100, 150);
//displays on first frame
canvas['canvas1'].ctx.fillStyle = "rgba(255, 255, 255, 0.8)";
canvas['canvas1'].ctx.fillRect (30, 30, 55, 50);
FillRect works fine but not the drawImage for the first frame, any ideas why this might be ??

I think you should start the animation loop by calling requestAnimFrame outside of your gameUpdate function
requestAnimFrame(gameUpdate)
You might also need to make sure the image is actually loaded
var img = new Image();
img.onload = function() {
// do the draw image here
}
img.src = 'images/0.png'; //preLoad the image

Related

Strange 700ms delay when using context.drawImage();

I'm working on a small canvas animation that requires me to step through a large sprite sheet png so I'm getting a lot of mileage out of drawImage(). I've never had trouble in the past using it, but today I'm running into an odd blocking delay after firing drawImage.
My understanding is that drawImage is synchronous, but when I run this code drawImage fired! comes about 700ms before the image actually appears. It's worth noting it's 700ms in Chrome and 1100ms in Firefox.
window.addEventListener('load', e => {
console.log("page loaded");
let canvas = document.getElementById('pcb');
let context = canvas.getContext("2d");
let img = new Image();
img.onload = function() {
context.drawImage(
img,
800, 0,
800, 800,
0, 0,
800, 800
);
console.log("drawImage fired!");
};
img.src = "/i/sprite-comp.png";
});
In the larger context this code runs in a requestAnimationFrame loop and I only experience this delay during the first execution of drawImage.
I think this is related to the large size of my sprite sheet (28000 × 3200) # 600kb though the onload event seems to be firing correctly.
edit: Here's a printout of the time (ms) between rAF frames. I get this result consistently unless I remove the drawImage function.
That's because the load event only is a network event. It only tells that the browser has fetched the media, parsed the metadata, and has recognized it is a valid media file it can decode.
However, the rendering part may still not have been made when this event fires, and that's why you have a first rendering that takes so much time. (Though it used to be an FF only behavior..)
Because yes drawImage() is synchronous, It will thus make that decoding + rendering a synchrounous operation too. It's so true, that you can even use drawImage as a way to tell when an image really is ready..
Note that there is now a decode() method on the HTMLImageElement interface that will tell us exactly about this, in a non-blocking means, so it's better to use it when available, and to anyway perform warming rounds of all your functions off-screen before running an extensive graphic app.
But since your source image is a sprite-sheet, you might actually be more interested in the createImageBitmap() method, which will generate an ImageBitmap from your source image, optionally cut off. These ImageBitmaps are already decoded and can be drawn to the canvas with no delay. It should be your preferred way since it will also avoid that you draw the whole sprite-sheet every time. And for browsers that don't support this method, you can monkey patch it by returning an HTMLCanvasElement with the part of the image drawn on it:
if (typeof window.createImageBitmap !== "function") {
window.createImageBitmap = monkeyPatch;
}
var img = new Image();
img.crossOrigin = "anonymous";
img.src = "https://upload.wikimedia.org/wikipedia/commons/b/be/SpriteSheet.png";
img.onload = function() {
makeSprites()
.then(draw);
};
function makeSprites() {
var coords = [],
x, y;
for (y = 0; y < 3; y++) {
for (x = 0; x < 4; x++) {
coords.push([x * 132, y * 97, 132, 97]);
}
}
return Promise.all(coords.map(function(opts) {
return createImageBitmap.apply(window, [img].concat(opts));
})
);
}
function draw(sprites) {
var delay = 96;
var current = 0,
lastTime = performance.now(),
ctx = document.getElementById('canvas').getContext('2d');
anim();
function anim(t) {
requestAnimationFrame(anim);
if (t - lastTime < delay) return;
lastTime = t;
current = (current + 1) % sprites.length;
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
ctx.drawImage(sprites[current], 0, 0);
}
}
function monkeyPatch(source, sx, sy, sw, sh) {
return Promise.resolve()
.then(drawImage);
function drawImage() {
var canvas = document.createElement('canvas');
canvas.width = sw || source.naturalWidth || source.videoWidth || source.width;
canvas.height = sh || source.naturalHeight || source.videoHeight || source.height;
canvas.getContext('2d').drawImage(source,
sx || 0, sy || 0, canvas.width, canvas.height,
0, 0, canvas.width, canvas.height
);
return canvas;
}
}
<canvas id="canvas" width="132" height="97"></canvas>

How to stop a callback?

I have the following lines from a canvas sprite animation from Git.
I am just wondering how can I stop the animations once it ends the sprite.
window.requestAnimFrame = (function(callback) { // shim
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
function animate() { // Animation loop that draws the canvas
context.clearRect(0, 0, context.canvas.width, context.canvas.height); // Clear the canvas
spriteMap.draw(context, 100, 100); // Draw the sprite
requestAnimFrame(animate); // Run the animation loop
}
https://github.com/IceCreamYou/Canvas-Sprite-Animations
Use cancelAnimationFrame() together with the request-id returned by requestAnimationFrame():
var reqId;
function animate() {
// ...
reqId = requestAnimFrame(animate); // returns request ID
}
Then to stop:
cancelAnimationFrame(reqId);
If you depend on the polyfill you will also have to include the polyfill for cancelAnimationFrame():
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};

How to pause and resume a HTML5 animation?

I am using "smoothie charts" http://smoothiecharts.org/.
I'm trying to make the animation stop and restart, but all I can do is freeze the "viewing" image. The animation doesn't really stop. It seems to continue to run in the background?
Once I restart it, the entire chart jumps to actual time.
On "start" I need that animation to resume, from where it was paused.
How can I achieve this?
I'm new with to this and have been trying to figure it out for a week, but i'm stuck on this problem.
This is the code for animation:
SmoothieChart.AnimateCompatibility = (function() {
var requestAnimationFrame = function(callback, element) {
var requestAnimationFrame =
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
return window.setTimeout(function() {
callback(new Date().getTime());
}, 16);
};
return requestAnimationFrame.call(window, callback, element);
},
cancelAnimationFrame = function(id) {
var cancelAnimationFrame =
window.cancelAnimationFrame ||
function(id) {
clearTimeout(id);
};
return cancelAnimationFrame.call(window, id);
};
return {
requestAnimationFrame: requestAnimationFrame,
cancelAnimationFrame: cancelAnimationFrame
};
})();
And here is the original stop...
SmoothieChart.prototype.stop = function() {
if (this.frame) {
SmoothieChart.AnimateCompatibility.cancelAnimationFrame(this.frame);
delete this.frame;
}
};
This is the start function
SmoothieChart.prototype.start = function() {
if (this.frame) {
// We're already running, so just return
return;
}
// Make sure the canvas has the optimal resolution for the device's pixel ratio.
if (this.options.enableDpiScaling && window && window.devicePixelRatio !== 1) {
var canvasWidth = this.canvas.getAttribute('width');
var canvasHeight = this.canvas.getAttribute('height');
this.canvas.setAttribute('width', canvasWidth * window.devicePixelRatio);
this.canvas.setAttribute('height', canvasHeight * window.devicePixelRatio);
this.canvas.style.width = canvasWidth + 'px';
this.canvas.style.height = canvasHeight + 'px';
this.canvas.getContext('2d').scale(window.devicePixelRatio, window.devicePixelRatio);
}
// Renders a frame, and queues the next frame for later rendering
var animate = function() {
this.frame = SmoothieChart.AnimateCompatibility.requestAnimationFrame(function() {
this.render();
animate();
}.bind(this));
}.bind(this);
animate();
};

How do I detect if a browser tab is currently visible and rendering using Javascript?

I'm animating some image presentation using
setInterval(function() {
$('.img').css('someprop', randomValue());
}, 2000);
..where .img has css transitions enabled, hence animation.
When i go over to another tab for a few minutes and come back to this tab, the animations go crazy for 5-6 seconds and catch up with everything at once.
Is there a way for me to stop the accumulation of undisplayed animations while the tab is not visible? What's the right approach to solve this? I understand the browser stops the animations while a window is not rendering, for performance reasons, but is there a way for me to tell it not to try to catch up with everything it "missed out on" ?
window.requestAnimationFrame does exactly what you want, only animating/running when the tab is "active" (visible).
See the MDN page on requestAnimationFrame for more details.
Example code by Paul Irish, posted here for posterity (here's a link to his explanation page)
// requestAnim shim layer by Paul Irish
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(/* function */ callback, /* DOMElement */ element){
window.setTimeout(callback, 1000 / 60);
};
})();
// example code from mr doob : http://mrdoob.com/lab/javascript/requestanimationframe/
var canvas, context, toggle;
init();
animate();
function init() {
canvas = document.createElement( 'canvas' );
canvas.width = 512;
canvas.height = 512;
context = canvas.getContext( '2d' );
document.body.appendChild( canvas );
}
function animate() {
requestAnimFrame( animate );
draw();
}
function draw() {
var time = new Date().getTime() * 0.002;
var x = Math.sin( time ) * 192 + 256;
var y = Math.cos( time * 0.9 ) * 192 + 256;
toggle = !toggle;
context.fillStyle = toggle ? 'rgb(200,200,20)' : 'rgb(20,20,200)';
context.beginPath();
context.arc( x, y, 10, 0, Math.PI * 2, true );
context.closePath();
context.fill();
}

Background image translate?

I'm attempting to get basic side scroller movement down in canvas, and I'm in a good spot with the movement itself, but I can't seem to get the background to translate. Perhaps I'm misunderstanding how translate works? The main canvas is translating fine (the one the 'player' is on) but the bg canvas won't budge.
http://jsfiddle.net/Y5SG8/1/
Fullscreen: http://jsfiddle.net/Y5SG8/1/embedded/result/
Any help would be greatly appreciated.
(function() {
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
window.requestAnimationFrame = requestAnimationFrame;
})();
var canvas = document.getElementById('canvas'),
bg = document.getElementById('canvas2'),
ctx = canvas.getContext('2d'),
bgctx = bg.getContext('2d'),
width = 1280,
height = 720,
player = {
x: width/2,
y: height/2 - 15,
width: 16,
height: 24,
speed: 3,
velx: 0,
vely: 0,
jumping: false
},
keys = [],
friction = 0.9,
gravity = 0.3;
canvas.addEventListener('keydown', function(e) {keys[e.keyCode] = true;})
canvas.addEventListener('keyup', function(e) {keys[e.keyCode] = false;})
canvas.width = width;
canvas.height = height;
bg.width = width;
bg.height = height;
var bgimg = new Image();
bgimg.src = 'bg.png';
bgimg.onload = function bgload() {bgctx.drawImage(bgimg,0,0);}
function playerupdate() {
if (keys[68]) {
if (player.velx < player.speed) {player.velx++;}
}
if (keys[65]) {
if (player.velx > -player.speed) {player.velx--;}
}
player.velx *= friction;
player.x += player.velx;
ctx.translate(-player.velx,0);
bgctx.translate(player.velx,0);
ctx.clearRect(player.x, player.y, player.width, player.height);
ctx.fillStyle = '#FF0000'
ctx.fillRect(player.x, player.y, player.width, player.height);
requestAnimationFrame(playerupdate);
console.log(player.x)
}
window.onload = playerupdate();
Short answer, the translate function translates the context of the canvas, but it does not redraw, so you'd need to:
// note you probably want the ctx to translate in the opposite direction of
// the player's velocity if you want the appearance of movement (if that's
// what you want)
bgctx.translate(-player.velx, 0);
bgctx.clearRect(0, 0, bg.width, bg.height);
bgctx.drawImage(bgimg, 0, 0);
Knowing that, you can probably figure it out from there. If your background is non-repeating (and you prevent the player from moving off the edges), then this might be the solution.
If your background is repeatable, you'll need to do a bit more work, as translating the image will quickly move it off screen. You can solve this by drawing a repeating fill created from the image rather than drawing in the image itself, something like:
// on image load, replacing your initial `drawImage`
bgimg.onload = function bgload() {
var ptrn = bgctx.createPattern(bgimg, 'repeat');
bgctx.fillStyle = ptrn;
bgctx.fillRect(0, 0, bg.width, bg.height);
}
// then in the loop
bgctx.translate(-player.velx, 0);
// Here you'd fill a square *around* the player, instead of just
// repainting the image.
bgctx.fillRect(player.x - width/2, 0, bg.width, bg.height);
As #numbers1311407 says in his answer you will need to redraw the image.
But translate is strictly not necessary here - just redraw the image into a new position instead.
bgctx.drawImage(bgimg, -player.velx, 0);
Modified fiddle
You don't even need to use clear as the image will overdraw anything - the only thing you need to take care of is wrapping/tiling when the image is out of "bound" or you will get tearing (that applies to both approaches).

Categories

Resources