I have been trying to draw a tile-like map to the canvas, although if i try to draw the same sprite twice, it will only render the final call of drawImage. Here is my code if it helps :
window.onload = function(){
function get(id){
return document.getElementById(id);
}
var canvas = get("canvas");
ctx = canvas.getContext("2d");
canvas.width = 160;
canvas.height = 160;
grass = new Image();
water = new Image();
grass.src = "res/Grass.png";
water.src = "res/Water.png";
path = {
draw: function(image,X,Y){
X = (X*32)-32;
Y = (Y*32)-32;
image.onload = function(){
ctx.drawImage(image,X,Y);
}
},
}
path.draw(water,2,2);
path.draw(grass,1,2);
path.draw(grass,1,1);
path.draw(water,2,1);
}
You're overwriting onload on the image next time you call it.
Think of it this way:
var image = {
onload: null
};
// Wait for the image to "load"
// Remember, onload is asynchronous so it won't be called until the rest of
// your code is done and the image is loaded
setTimeout(function() {
image.onload();
}, 500);
image.onload = function() {
console.log('I will never be called');
};
image.onload = function() {
console.log('I overwrite the previous one');
};
Instead, you might try waiting for your images to load then do the rest of your logic.
var numOfImagesLoading = 0;
var firstImage = new Image();
var secondImage = new Image();
// This is called when an image finishes loading
function onLoad() {
numOfImagesLoading -= 1;
if (numOfImagesLoading === 0) {
doStuff();
}
}
function doStuff() {
// This is where you can use your images
document.body.appendChild(firstImage);
document.body.appendChild(secondImage);
}
firstImage.src = 'http://placehold.it/100x100';
firstImage.onload = onLoad;
numOfImagesLoading += 1;
secondImage.src = 'http://placehold.it/200x200';
secondImage.onload = onLoad;
numOfImagesLoading += 1;
UPDATED
I have an HTML code below:
<canvas id="canvas1"></canvas>
<canvas id="canvas2"></canvas>
I have a function below:
var Context;
function onSign(canvas){
var ctx = document.getElementById(canvas).getContext('2d');
SigWebSetDisplayTarget(ctx);
tmr = setInterval(SigWebRefresh, 50);
}
function SigWebSetDisplayTarget( obj ){
Context = obj;
}
I called the function OnSign twice with different canvas id parameters.
OnSign('canvas1');
OnSign('canvas2');
Below is the SigWebRefresh function that is repeatedly called for a reason.
function SigWebRefresh(){
xhr2 = new XMLHttpRequest();
requests.push(xhr2);
xhr2.open("GET", baseUri + "SigImage/0", true );
xhr2.responseType = "blob"
xhr2.onload = function (){
var img = new Image();
img.src = 'image.jpg';
img.onload = function (){
Ctx.drawImage(img, 0, 0);
revokeBlobURL( img.src );
img = null;
}
}
xhr2.send(null);
}
After that, I noticed that the two canvas was being updated and the image is loaded to the 2 canvas. Why is it? I have to load the image, only to the last canvas I supplied with the function OnSign. Where am I missing?
var Context; is declared in global scope
OnSign('canvas1');
OnSign('canvas2');
Doing so you are assigning value of canvas number two.
Update: http://jsfiddle.net/bmynvtLd/2/
This is what you desired to update both contexts? if yes this is how you pass the parameter to setInterval function: setInterval(function() {SigWebRefresh(ctx)}, 300);
running example:
var images = ['http://img09.deviantart.net/6d02/i/2015/284/0/b/beginning_autumn_by_weissglut-d9cssxw.jpg', 'http://orig06.deviantart.net/063c/f/2013/105/3/6/free_crystal_icons_by_aha_soft_icons-d61tfi1.png', 'http://img15.deviantart.net/b126/i/2015/118/c/1/this_small_tree_by_weissglut-d8re8q7.jpg', 'http://img00.deviantart.net/84f8/i/2015/284/d/f/nuclear_light_by_noro8-d9cs4u6.jpg'];
function OnSign(canvas){
var ctx = document.getElementById(canvas).getContext('2d');
tmr = setInterval(function() {SigWebRefresh(ctx)}, 300);
}
OnSign('canvas1');
OnSign('canvas2');
var index = 0;
function MyIndex()
{
// console.log(index);
index = images.length == index ? 0 : index+1;
return images[index];
}
function SigWebRefresh(Context){
var img = new Image();
img.src = MyIndex();
img.onload = function (){
Context.drawImage(img, 0, 0);
img = null;
}
}
So I have two functions. One to load the image and the other to resize its container element. Naturally the image element needs to be loaded, before any measurements can be taken. It looks something like this:
var imgEl;
loadImage(imgSrc);
// only call the below, once the above has finished loading and appending the image.
resizeModal();
function loadImage(imgSrc) {
var image = new Image();
image.src = imgSrc;
image.onload = function() {
imgEl = $('<img src='+image.src+'/>');
imgEl.prependTo(modal);
}
}
function resizeModal() {
// Do stuff based off the imgEl once it's been loaded and appended
var width = imgEl.width();
modal.width(width)
}
I've tried using $.Deferred, but I'm apparently missing something, as "B" always gets logged before "A":
var imgEl;
loadImage(imgSrc).done( resizeModal() )
function loadImage(imgSrc) {
var def = $.Deferred();
var image = new Image();
image.src = imgSrc;
image.onload = function() {
imgEl = $('<img src='+image.src+'/>');
imgEl.prependTo(modal);
console.log("A");
def.resolve();
}
return def;
}
function resizeModal() {
console.log("B");
// Do stuff based off the imgEl once it's been loaded and appended
var width = imgEl.width();
modal.width(width)
}
That's because you are explicitly calling resizeModal before the promise is resolved:
loadImage(imgSrc).done( resizeModal() )
Just like with foo(bar()), this will call resizeModal and pass its return value to done().
You want to pass the function itself instead:
loadImage(imgSrc).done(resizeModal)
This basically means "call resizeModal once you are done".
var loadImage = function(imgSrc, callback) {
var imgEl;
var image = new Image();
image.src = imgSrc;
image.onload = function() {
imgEl = $('<img src='+image.src+'/>');
imgEl.prependTo(modal);
}
callback(imgEl);
}
var resizeModal = function(imgEl) {
// Do stuff based off the imgEl once it's been loaded and appended
var width = imgEl.width();
modal.width(width)
return width; // or whatever you are trying to get from this.
}
var finalOutput = loadImage(imgSrc, resizeModal);
Have you tried a structure like this?
Currently having some issues with drawImage();. Namely it wont actually draw. I tried it out with fillRect(); and it worked aswell as putting the drawImage(); inside the the onload function aswell (which worked).
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.width = 640;
canvas.height = 400;
document.body.appendChild(canvas);
var tileArray = [
[0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,1,1,1,1,0,0,1,0,0,0],
[0,0,0,1,1,1,1,0,1,1,1,0,0],
[0,0,0,1,1,1,0,0,0,0,1,1,0],
[0,0,0,1,1,1,0,0,0,0,0,0,0],
[0,0,0,1,1,1,0,0,0,0,0,0,0],
[0,0,0,0,1,1,1,0,0,0,0,0,0],
[0,0,0,0,1,1,1,0,0,0,0,0,0],
[0,0,0,0,0,1,1,1,0,0,0,0,0],
[0,0,0,0,0,0,1,1,0,0,0,0,0]
];
var grassReady = false;
var grass = new Image();
grass.onload = function() {
grassReady = true;
};
grass.src = "images/grass.png";
var sandReady = false;
var sand = new Image();
sand.onload = function() {
sandReady = true;
};
sand.src = "images/sand.png";
var posX = 0;
var posY = 0;
if(grassReady) {
ctx.drawImage(grass, posX, posY);
}
Any pointers as to why this is would be greatly appreciated and I appologize in advance if messed up the code section in anyway. I went through other similar posts and coulden't find a solution that seemed to work.
As #Suman Bogati correctly says, you must wait for your images to load before using them in drawImage.
A Demo: http://jsfiddle.net/m1erickson/jGPGj/
Here's an image loader that preloads all images and then calls the start() function where you can use drawImage because all the images are fully loaded.
var imageURLs=[]; // put the paths to your images here
var imagesOK=0;
var imgs=[];
imageURLs.push("images/grass.png");
imageURLs.push("images/sand.png");
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(){
// the imgs[] array holds fully loaded images
// the imgs[] are in the same order as imageURLs[]
// grass.png is in imgs[0]
// sand.png is in imgs[1]
}
This statement ctx.drawImage(); should be inside the grass.onload = function() {} function, something like
grass.onload = function() {
ctx.drawImage(grass, posX, posY);
}
If you define drawImage() outside the grass.onload() function, then that statment would executed first, so at that point grassReady is false, So the condition is not satisfied.
Bascially it's related to asynchronous concept.
Your code is running into order
1) First
var grassReady = false;
if(grassReady) {
//grassReady is false, this condition is not satisfied
ctx.drawImage(grass, posX, posY);
}
2) Second
grass.onload = function() {
grassReady = true;
};
I am playing with the canvas, and it seems to be working great in FF6, but in Chrome 13, the sprite that I am drawing does not appear reliably. I have done some research and found that the problem stems from the function firing before the asset has loaded completely.
Fiddle here:
http://jsfiddle.net/LqHY9/
Relevant Javascript:
function sprite(ipath, sh, sw, ih, iw){
/* BASIC INFO FOR SPRITE */
this.frameWidth = sw;
this.frameHeight= sh;
frame_rows = ih/sh;
frame_columns = iw/sw;
num_frames = frame_columns*frame_rows ;
this.frame = new Array();
frameNumber = 0;
for(row = 0; row<frame_rows; row++){
for(i=0;i<frame_columns;i++){
this.frame[frameNumber] = {};
this.frame[frameNumber].offsetX = this.frameWidth*i;
this.frame[frameNumber].offsetY = this.frameHeight*row;
frameNumber++
}
}
this.sheight = sh;
this.swidth = sw;
this.raw = new Image();
this.raw.src = ipath;
}
animation=new sprite("http://www.melonjs.org/tutorial/tutorial_final/data/sprite/gripe_run_right.png",64,64,64,512);
context.drawImage(animation.raw, animation.frame[0].offsetX, animation.frame[0].offsetY, animation.frameWidth, animation.frameHeight, 0, 0, animation.frameWidth,animation.frameHeight)
(Don't worry, my context variable is defined, I just cut that bit out, you can see the whole thing in the JSFiddle.)
The Image object has an onload event which you should hook into.
Assuming you have more than one image, you could implement a sort of a "loader". This would basically just take an array of image URLs, load each of them, and listen to their onload events. Once each image has loaded, it would in turn call some other function, which would signal that every resource has finished loading.
the Image() object has an onload(and onerror) event. If you need to execute something after it loads you can attach a function.
e.g.
var img = new Image();
img.onload = function() {
//do something
};
Just make sure you attach the onload before setting the src.
You need to use the onload handler for the image. You must set the handler before you set the .src for the object because in some browsers, the load event may fire immediately upon setting .src if the image is in the browser cache. Here's a piece of pseudo code:
var img = new Image();
img.onload = function () {
// image is now loaded and ready for handling
// you can safely start your sprite animation
}
img.src = "xxx";
You can see related sample code from another answer I wrote here: jQuery: How to check when all images in an array are loaded?.
function Sprite(urls, speed, box)
{
var that = this, running = false, interval = 0, loaded = false;
that.urls = urls;
that.speed = speed;
that.images = new Array();
that.box = box || { x: 0.0, y: 0.0, w: 64, h: 64 };
for(var i = 0; i < that.urls.length; ++i)
{
that.images[i] = new Image();
that.images[i].src = that.urls[i];
that.images[i].id = i;
var len = that.urls.length;
that.images[i].onload = function(){ if(parseInt(this.id) === len) { loaded = true; } };
}
that.current = 0;
var Draw = function(ctx)
{
if(loaded)
{
var curr = that.images[that.current];
ctx.drawImage(curr, 0.0, 0.0, curr.width, curr.height, that.box.x, that.box.y, that.box.w, that.box.h);
}
};
that.Run = function(ctx)
{
if(!running)
{
running = true;
interval = setInterval(function(){
Draw(ctx);
if(that.current < that.urls.length)
{
that.current++;
}
else
{
that.current = 0;
}
}, that.speed);
}
};
that.Clear = function()
{
if(running)
{
running = false;
clearInterval(interval);
}
};
}
// Exemple
var test = new Sprite(["image1.png", "image2.png", "image3.png"], 250);
test.Run(myContext);