Canvas Image Animation (Crossfade) - javascript

Using HTML5 canvas I'm trying to load images and create a 'crossfade' effect where the first image fades into view then, after a short delay, the second image fades in over top of first image, etc.
With the help of answers to similar questions on this forum I've got 2 separate bits of code working ... one which loads an array of images and a second which animates a 'fade in' effect. My problem is that I don't know how to combine these 2 scripts to load an array of images AND ALSO have each image in array fade in as it loads.
Here are the 2 separate scripts I've got working:
LOAD IMAGE ARRAY INTO CANVAS:
HTML
<canvas id="canvas" width=600 height=350></canvas>
JS
window.onload = function() {
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var delay=2000;
var nextTime=0;
var nextImage=0;
var imageURLs=[];
imageURLs.push("img/sunflower0.jpg");
imageURLs.push("img/sunflower1.jpg");
imageURLs.push("img/sunflower2.jpg");
var imgs=[];
var imagesOK=0;
loadAllImages(start);
function loadAllImages(callback){
for (var i=0; i<imageURLs.length; i++) {
var img = new Image();
imgs.push(img);
img.onload = function(){
imagesOK++;
if (imagesOK >= imageURLs.length ) {
callback();
}
};
img.src = imageURLs[i];
}
}
function start(){
requestAnimationFrame(animate);
}
function animate(currentTime){
requestAnimationFrame(animate);
if(currentTime<nextTime){return;}
ctx.clearRect(0,0,cw,ch);
ctx.drawImage(imgs[nextImage],0,0);
nextTime=currentTime+delay;
nextImage++;
if(nextImage>imgs.length-1){nextImage=0;}
}
} // close window.onload
FADE IN IMAGES AS THEY LOAD INTO CANVAS:
I managed to get a separate bit of code working that does this using Canvas and Greensock TweenMax:
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.18.0/TweenMax.min.js"> </script>
<script>
var ctx, img;
function init() {
ctx = document.getElementById("canvas").getContext("2d");
img = new Image();
img.src = "img/sunflower0.jpg";
img.xpos = 0;
img.ypos = 0;
img.globalAlpha = 0;
img.onload = function() {
TweenMax.ticker.addEventListener("tick",loop);
}
TweenMax.to(img, 5 ,{globalAlpha:1});
}
function loop(){
ctx.clearRect(0,0,500,336);
ctx.globalAlpha = img.globalAlpha;
ctx.drawImage(img, img.xpos, img.ypos);
}
init();
Can anyone show me how to combine these two scripts to get a crossfade effect?
Many thanks!

I would use three canvases for this :
var imgs = [];
var rand = Math.random;
var common = "http://lorempixel.com/500/300?";
var imageURLs = [common + rand(), common + rand(), common + rand(), common + rand(), common + rand()];
var imagesOK = 0;
function loadAllImages(callback) {
for (var i = 0; i < imageURLs.length; i++) {
var img = new Image();
imgs.push(img);
img.onload = function() {
imagesOK++;
if (imagesOK >= imageURLs.length) {
callback();
}
};
img.src = imageURLs[i];
}
}
var ctx = main.getContext('2d');
var last = main.cloneNode(true).getContext('2d');
var next = main.cloneNode(true).getContext('2d');
var current = 0;
var op = 1;
function nextImage() {
if (current++ >= imgs.length - 1) current = 0;
op = 1;
fade();
}
function fade() {
op -= .01;
last.clearRect(0, 0, main.width, main.height);
last.globalAlpha = op;
last.drawImage(imgs[current], 0, 0);
next.clearRect(0, 0, main.width, main.height);
next.globalAlpha = 1 - op;
next.drawImage(imgs[(current + 1) % (imgs.length)], 0, 0);
ctx.clearRect(0, 0, main.width, main.height);
ctx.drawImage(last.canvas, 0, 0);
ctx.drawImage(next.canvas, 0, 0);
if (op <= 0) setTimeout(nextImage, 1500);
else requestAnimationFrame(fade);
}
loadAllImages(fade);
<canvas id="main" width="500" height="300"></canvas>

Related

Move different images across a canvas on a timer/delay

I wish to be able to move some images say 5/6 across the canvas. The position of the image will obviously change each time, simulating basic animation. The images will be on a timer/ delay so that they move every second or so. Is there a function that i can create so that i dont have to repeat the same code over and over again for the images. Also it would be good if the images were preloaded but im unsure on how to manipulate the image once it has been preloaded.
If there was also a way of using requestAnimationFrame instead of setTimeout then that would be even better.
Heres my current code:
window.addEventListener("load", eventWindowLoaded, false);
function eventWindowLoaded () {
images = new Array();
images[0] = "images/1.png";
images[1] = "images/2.png";
images[2] = "images/3.png";
images[3] = "images/4.png";
images[4] = "images/5.png";
imageObjs = [];
for(var i = 0; i <= 4; i++){
var imageObj = new Image();
imageObj.src = images[i];
imageObjs.push(imageObj);
}
drawFrame();
}
function drawFrame () {
setTimeout(function() {
image = new Image();
image.onload = function() {
ctx.drawImage(imageObjs[0], 0, 0);
}
image.src = "images/1.png";
}, 200);
setTimeout(function () {
image2 = new Image();
image2.onload = function() {
ctx.drawImage(imageObjs[1], 200, 0);
};
image2.src = "images/2.png";
}, 1000);
}
You expressed interest in having code do the following:
Preload all images.
Move images across the canvas.
Use an animation loop function so code is not repeated.
Use requestAnimationFrame instead of setTimeout for efficiency.
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var images=[];
images.push({x:20,y:200,moveX:5,maxX:250,delay:100,nextTime:0,url:'https://dl.dropboxusercontent.com/u/139992952/multple/cars1.png'});
images.push({x:20,y:30,moveX:10,maxX:100,delay:500,nextTime:0,url:'https://dl.dropboxusercontent.com/u/139992952/multple/sun.png'});
var imageCount=images.length;
for(var n=0;n<images.length;n++){
var i=images[n];
i.img=new Image();
i.img.onload=start;
i.img.src=i.url;
}
function start(){
if(--imageCount>0){return;}
requestAnimationFrame(animate);
}
function animate(time){
requestAnimationFrame(animate);
var needsRedrawing=false;
for(var n=0;n<images.length;n++){
var i=images[n];
if(time>i.nextTime){
if(i.x+i.moveX<i.maxX){
i.x+=i.moveX;
i.nextTime+=i.delay;
needsRedrawing=true;
}
}
}
if(needsRedrawing){
ctx.clearRect(0,0,cw,ch);
for(var n=0;n<images.length;n++){
var i=images[n];
ctx.drawImage(i.img,i.x,i.y);
}
}
}
body{ background-color: ivory; padding:10px; }
#canvas{border:1px solid red;}
<canvas id="canvas" width=300 height=300></canvas>

Src Canvas Flickering

I am trying to figure out how to switch images on a canvas without a small time of blankness in between.
To make my point here is an extreme example of this. In this program, when your spacebar is held down it redraws the image circle.png every milisecond. Because this is so fast the image dissappears.
I did try to preload my image but it didn't help.
Here is my full code:
// Access Canvas
var canvas = document.getElementById("gameBoard");
var ctx = canvas.getContext("2d");
// preload image
var circleReady = false;
var circleImage = new Image();
circleImage.onload = function () {
circleReady = true;
};
circleImage.src = "images/Circle.png";
// Game objects
var circle = {
};
// circle location
circle.x = canvas.width / 2;
circle.y = canvas.height / 2;
// Keyboard events
var keysDown = {};
addEventListener("keydown", function (e) {
keysDown[e.keyCode] = true;
}, false);
addEventListener("keyup", function (e) {
delete keysDown[e.keyCode];
}, false);
// Update Objects
var update = function () {
if (32 in keysDown) { // Player space pressed
circleImage.src = "images/Circle.png"; //re-draws image
}
};
// Draws Everything
var render = function () {
ctx.fillStyle = "#FFFFFF";
ctx.fillRect(0,0,600,609);
if (circleReady) {
ctx.drawImage(circleImage, circle.x, circle.y);
}
};
// The main loop
var main = function () {
update();
render();
};
// Starts Function
var then = Date.now();
setInterval(main, 1); // Execute as fast as possible
You are reloading the image in the update function which is causing your delay:
circleImage.src = "images/Circle.png"; //re-draws image
All you have to do is drawImage without reloading the image:
ctx.drawImage(circleImage, circle.x, circle.y);
Here is an example of an image loader that loads all your images before execution begins:
var imageURLs=[]; // put the paths to your images here
var imagesOK=0;
var imgs=[];
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/stackoverflow/house1.jpg");
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/stackoverflow/house2.jpg");
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/stackoverflow/house3.jpg");
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/stackoverflow/house4.jpg");
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/stackoverflow/house5.jpg");
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/stackoverflow/house6.jpg");
loadAllImages(start);
function loadAllImages(callback){
for (var i=0; i<imageURLs.length; i++) {
var img = new Image();
imgs.push(img);
img.onload = function(){
imagesOK++;
if (imagesOK>=imageURLs.length ) {
callback();
}
};
img.onerror=function(){alert("image load failed");}
img.crossOrigin="anonymous";
img.src = imageURLs[i];
}
}
function start(){
// all your images are fully loaded so begin your app
}
You can have two canvases, one for each image. When you want to switch. Remove or hide the one in the front, leaving (instantly) the one in the back.
Per your comment, here is an example of 26 letters of the alphabet. We only keep two canvases at a time. Each time the user clicks whe add a new only, and make the hidden one visible, with no delay.
function createCanvas(letter) {
var canvas = document.createElement('canvas');
canvas.id = letter;
canvas.style.display = 'none';
document.getElementById('container').appendChild(canvas);
var image = new Image();
image.src = 'http://icons.iconarchive.com/icons/iconicon/alpha-magnets/128/Letter-'+letter+'-icon.png';
image.onload = function() {
canvas.getContext('2d').drawImage(this, 0, 0);
};
return canvas;
}
createCanvas('a').style.display = '';
createCanvas('b');
var prevCharCode = 'a'.charCodeAt(0);
var charCode = 'b'.charCodeAt(0);
document.addEventListener('click', function() {
document.getElementById(String.fromCharCode(charCode)).style.display = '';
document.getElementById('container').removeChild(
document.getElementById(String.fromCharCode(prevCharCode))
);
prevCharCode = charCode;
if(++charCode > 'z'.charCodeAt(0)) {
charCode = 'a'.charCodeAt(0);
}
createCanvas(String.fromCharCode(charCode));
});

HTML5 canvas how to draw one picture on top of another

I want to draw two images on the same canvas. The first image is background.jpg and the second is photo.jpg. I want the photo.jpg always on top of the other:
var ctx = document.getElementById("main").getContext("2d");
var background = new Image();
var photo = new Image();
background.onload = function() {
ctx.drawImage(background, 0, 0);
}
photo.onload = function() {
ctx.drawImage(photo, 0, 0);
}
background.src = "background.jpg";
photo.src = "photo.jpg"
My question is how can I make sure the photo is always on the top. Because the onload are callbacks, I cannot make any assumptions about the calling order. Thanks!
Store your images in an array instead. That will makes sure the order is kept no matter which image finish loading first:
var ctx = document.getElementById("main").getContext("2d");
var background = new Image();
var photo = new Image();
var images = [background, photo]; /// the key
var count = images.length;
background.onload = photo.onload = counter;
background.src = "background.jpg";
photo.src = "photo.jpg"
/// common loader keeping track if loads
function counter() {
count--;
if (count === 0) drawImages();
}
/// is called when all images are loaded
function drawImages() {
for(i = 0; i < images.length; i++)
ctx.drawImage(images[i], 0, 0);
}
(the draw method assumes all being drawn at position 0,0 - of course, change this to meet your criteria).
The foreground could be loaded in the callback for the background
var ctx = document.getElementById("main").getContext("2d");
var background = new Image();
var photo = new Image();
background.onload = function() {
ctx.drawImage(background, 0, 0);
photo.src = "photo.jpg" // after background is loaded, load foreground
}
photo.onload = function() {
ctx.drawImage(photo, 0, 0);
}
background.src = "background.jpg";

Javascript Image() doesn't load or draw

I'm trying to create a simple Sprite class for use in a canvas game, but the image I specify when I create a new instance of the class never calls onload or onerror.
What's happening, and how do I get it to load and draw the image?
Here's the script:
var cvs = document.getElementById("c");
var ctx = cvs.getContext("2d");
var imagesToLoad = 1 ;
var imagesLoaded = 0;
var gameState = 0;
//START OF IMAGE STUFF//
function imageOnload() {
imagesLoaded+=1;
console.log("Images: loaded "+imagesLoaded+" overall.");
}
function loadLoop() {
if (imagesLoaded == imagesToLoad) {
gameState = 1;
}
}
function loop1() {
ctx.drawImage(circle.image);
}
function gameLoop() {
if(gameState==0){
loadLoop();
}
if(gameState==1){
loop1();
}
}
function Sprite(positionsArray){
this.x = positionsArray[0];
this.y = positionsArray[1];
this.dx = positionsArray[2];
this.dy = positionsArray[3];
this.angle = positionsArray[4];
this.rotating = positionsArray[5];
};
circle = new Sprite(new Array(0, 0, 0, 0, 0, 0));
circle.image = new Image().onload="imageOnload()".src="circle.png".onerror="console.log(\"Nope.\")";
console.log("circle?");
setInterval(function(){gameLoop()}, 10);
//END OF IMAGE STUFF//
Corrections:
No quotes on image.onload function definition
No parens on image.onload function definition
image.onerror requires a function definition or anonymous function
Put image.onerror before image.src
Here's some example code:
circle.image=new Image();
circle.image.onload=imageOnLoad;
circle.image.onerror=function(){console.log("Image failed to load")};
circle.image.src="koolaidman.png";
function imageOnLoad(){
ctx.drawImage(circle.image,0,0); // or do other stuff
}

HTML5 Canvas flicker in FireFox 4

I'm working on a proof of concept on an HTML5 canvas. I was able to get this working like a charm in Chrome and IE9, but in Firefox 4 I'm getting constant flicker as it redraws the canvas. I've tried a few techniques mentioned on this site like double buffering but I'm still getting a large amount of flicker. Any insight on this would be appreciated!
<!DOCTYPE HTML>
<html>
<head>
<style type="text/css">
body
{
margin:0px;
padding:0px;
text-align:center;
}
canvas
{
outline:0;
border:1px solid #000;
margin-top: 10px;
margin-left: auto;
margin-right: auto;
}
</style>
<script type="text/javascript">
var canvasWidth = 640;
var thisXPos = 0;
var canvasHeight = 820;
var thisYPos = 0;
var canvas = null;
var context = null;
var gLoop = null;
var rain = [];
var rainImg = "images/raindrop.gif";
var bgImg = null;
var i;
for (i = 0; i < howManyLetters; i++)
{
rain.push([Math.floor(Math.random() * canvasWidth), Math.floor(Math.random() * canvasHeight),rainImg]);
}
var DrawRain = function()
{
for (i = 0; i < 10; i++)
{
thisXPos = rain[i][0];
thisYPos = rain[i][1];
imgSrc = rain[i][2];
letterImg = new Image();
letterImg.setAtX = thisXPos;
letterImg.setAtY = thisYPos;
letterImg.onload = function()
{
context.drawImage(this, this.setAtX, this.setAtY);
}
letterImg.src = imgSrc;
}
};
var MoveRain = function(e)
{
for (i = 0; i < 10; i++)
{
if ((rain[i][1] - 5) > canvasHeight)
{
randomnumber = Math.floor(Math.random()*26);
rain[i][0] = Math.random() * canvasWidth;
rain[i][1] = 0;
rain[i][2] = rainImg;
}
else
{
rain[i][1] += e;
}
}
};
var clear = function()
{
context.beginPath();
context.rect(0, 0, canvasWidth, canvasHeight);
context.closePath();
bgImg = new Image();
bgImg.src = "images/bg.png";
bgImg.onload = function()
{
context.drawImage(bgImg,0,0);
}
}
var GameLoop = function()
{
context.save();
clear();
MoveRain(1);
DrawRain();
context.restore();
gLoop = setTimeout(GameLoop, 10);
}
function loadGame()
{
canvas = document.getElementById("gameCanvas");
context = canvas.getContext("2d");
canvas.width = canvasWidth;
canvas.height = canvasHeight;
GameLoop();
}
</script>
</head>
<body onload="loadGame();">
<canvas id="gameCanvas"></canvas>
</body>
</html>
I have distilled your example down to this:
http://jsfiddle.net/sPm3b/6/
And it works very fast in Firefox and Chrome.
So we know that the problem lies in the images.
You need to optimize how they are created and loaded. Right now, each clear() creates a new image and waits for it to load! That image should be created only once, in your loadGame() and then reused over and over.
Same exact deal with letterImg in DrawRain(). Move the creation of it to loadGame()
That will probably fix the problem.
EDIT:
like this:
At the top add:
var letterImg = new Image();
var bgImg = new Image();
Then
function loadGame()
{
canvas = document.getElementById("gameCanvas");
context = canvas.getContext("2d");
canvas.width = canvasWidth;
canvas.height = canvasHeight;
bgImg.src = "images/bg.png";
letterImg.src = "images/raindrop.gif";
// optional: wait for them to load here
GameLoop();
}
Then drawRain, for example, would look like this:
var DrawRain = function()
{
for (i = 0; i < 10; i++)
{
thisXPos = rain[i][0];
thisYPos = rain[i][1];
context.drawImage(letterImg, thisXPosX, thisYPos); // letterImg was already constructed, no need to make it again
}
};
In complement to Simon Sarris response. I've used a 'double canvas' technique to avoid screen fickering with heavy canvas.
The way it works is always have 2 version of the canvas, one in DOM, one outside, and always draw on the one which is not in DOM. I use it with a redraw queue.
here's a part of a working code
(...)
clear: function() {
//rotating on 2 canvas, one for draw (outside DOM) one for show
var self = this;
if (null == self.canvasbackup) {
var tmpcanvas = self.canvas.clone(true);
self.canvasbackup = self.canvas;
self.canvas=tmpcanvas;
} else {
var tmpcanvas = self.canvasbackup;
self.canvasbackup = self.canvas;
self.canvas=tmpcanvas;
}
self.ctx = self.canvas[0].getContext('2d');
self.ctx.clearRect( 0, 0, self.options.width, self.options.height );
jQuery.each(self.elements,function(idx,elt){
// custom function: my elements need to know which canvas they depends on
elt.reconnectCanvas(self.canvas,self.ctx);
});
},
inDOM: function() {
var self = this;
if(null==self.canvasbackup) {
//1st time need to get all things in DOM
self.canvas.appendTo(self.div);
self.div.appendTo(self.container);
} else {
// remove current shown canvas
self.connectHuman();
self.canvasbackup.remove();
// loosing some events here...
self.canvas.appendTo(self.div);
// div is already in DOM, we are in redraw
}
},
redraw: function() {
var self = this;
self.clear();
jQuery.each(self.elements,function(idx,elt){
elt.draw();
elt.enddraw();
});
self.inDOM();
}
(...)

Categories

Resources