I'm writing a simple 2D side scroller game using JavaScript + Canvas. I am trying to do this while learning OO JavaScript. I'm encountering an issue where my image will not draw to the canvas, despite being loaded correctly. Here's the code:
//===================================
// class - Canvas
//===================================
var classCanvas = function(id){
canvas = document.getElementById(id);
context = canvas.getContext("2d");
}
//===================================
// abstract class - Image Drawer
//===================================
var classImageDrawer = function(that){
this.draw = function(){
context.drawImage(that.img,
that.spriteCoordsX,
that.spriteCoordsY,
that.width,
that.height,
that.posX,
that.posY,
that.width,
that.height);
}
}
//===================================
// class - Level
//===================================
var classLevel = function(name, src){
this.name = name;
this.img = new Image();
this.img.src = src;
this.img.onload = function(){ };
this.spriteCoordsX = 0;
this.spriteCoordsY = 0;
this.posY = 0;
this.posX = 0;
this.height = 400;
this.width = 600;
this.drawer = new classImageDrawer(this);
}
//==================================
// Begin
//==================================
var game = new classCanvas("playground");
game.LevelOne = new classLevel("LevelOne", "images/foreground-land.png");
game.LevelOne.drawer.draw();
I have checked the code, and I know the image is getting loaded correctly. No errors occur during runtime, the image "foreground-land.png" simply DOESN'T show up on the canvas!
Your image may be loading fine, but as far as I can tell your call to game.LevelOne.drawer.draw() has no guarantee that foreground-land.png has loaded at that point (remember, images are loaded asynchronously without blocking the execution of other code).
Your onload function for the image object in your classLevel class is empty and anonymous. Nothing happens when the image is finished loading. I'm not sure how you want to structure your code given everything else you have going on, but you should only allow the call to game.LevelOne.drawer.draw() to succeed if the resources it depends upon have been fully loaded. The best way to do this would be to hook into the onload callback for all resources you require for that object (in this case, it's just the one image).
Loading images is an async operation, you are (basically) ignoring the loading process, and acting as though it is sync operations.
One thing you could look at is using a 3rd parameter as a callback ("LevelOne", "images/foreground-land.png", function() { this.drawer.draw() }) and doing that as the onload
Related
*Update with link to question I reference If I load multiple images with the for loop, do I only need one img.onload function?
I want to bring in many image files to use in canvas via an array of all the images (that way they load before I need them), Using a solution found on here I'm getting the error
"Uncaught TypeError: Cannot read property 'drawImage' of undefined
at Image. (app.js:32)" line 32 would be canvasContext[value].drawImage(images[i], 0, 0);
Obviously, I didn't understand the solution because I'm kind of at a loss as to what is causing this.
The end result would be for me to set up all the images on load and then in the draw function I have, declare their positions and sizing.
window.onload = function() {
console.log(keysDown);
canvas = document.getElementById('myCanvas');
canvasContext = canvas.getContext('2d');
var images = ['Frylock.png', 'Master_Shake.png'];
// Assign onload handler to each image in array
for (var i = 0; i <= images.length; i++) {
var img = new Image();
img.onload = (function(value) {
return function() {
canvasContext[value].drawImage(images[i], 0, 0);
}
})(i);
// IMPORTANT - Assign src last for IE
img.src = './' + images[i];
}
I have read countless of answers of this issue and I came up with the following, but it doesn't work either.
function fitToParent(objsParent, tagName) {
var parent, imgs, imgsCant, a, loadImg;
//Select images
parent = document.getElementById(objsParent);
imgs = parent.getElementsByTagName(tagName);
imgsCant = imgs.length;
function scaleImgs(a) {
"use strict";
var w, h, ratioI, wP, hP, ratioP, imgsParent;
//Get image dimensions
w = imgs[a].naturalWidth;
h = imgs[a].naturalHeight;
ratioI = w / h;
//Get parent dimensions
imgsParent = imgs[a].parentNode;
wP = imgsParent.clientWidth;
hP = imgsParent.clientHeight;
ratioP = wP / hP;
//I left this as a test, all this returns 0 and false, and they shouldn't be
console.log(w);
console.log(h);
console.log(ratioI);
console.log(imgs[a].complete);
if (ratioP > ratioI) {
imgs[a].style.width = "100%";
} else {
imgs[a].style.height = "100%";
}
}
//Loop through images and resize them
var imgCache = [];
for (a = 0; a < imgsCant; a += 1) {
imgCache[a] = new Image();
imgCache[a].onload = function () {
scaleImgs(a);
//Another test, this returns empty, for some reason the function fires before aplying a src to imgCache
console.log(imgCache[a].src);
}(a);
imgCache[a].src = imgs[a].getAttribute('src');
}
}
fitToParent("noticias", "img");
To summarise, the problem is the event onload triggers before the images are loaded (or that is how I understand it).
Another things to add:
I don't know at first the dimensions of the parent nor the child,
because they varied depending of their position on the page.
I don't want to use jQuery.
I tried with another function, changing the onload event to
window, and it worked, but it takes a lot of time to resize because
it waits for everything to load, making the page appear slower,
that's how I came to the conclusion the problem has something to do
with the onload event.
EDIT:
I made a fiddle, easier to look at the problem this way
https://jsfiddle.net/whn5cycf/
for some reason the function fires before aplying a src to imgCache
Well, the reason is that you are calling the function immedeatly:
imgCache[a].onload = function () {
}(a);
// ^^^ calls the function
You call the function and assign undefined (the return value of that function) to .onload.
If you want to use an IIFE to capture the current value of a, you have to make it return a function and accept a parameter to which the current value of a is assigned to:
imgCache[a].onload = function (a) {
return function() {
scaleImgs(a);
};
}(a);
Have a look again at JavaScript closure inside loops – simple practical example .
I have read all the other similar posts but I don't understand why it I have this problem.
I have a canvas (#canvas) and an image (hero.png) on the page, and a JS file loaded at the end of the body. In the JS code...
This works:
var game = {};
game.canvas = document.getElementById('canvas');
game.ctx = game.canvas.getContext("2d");
game.draw = function(){
game.ctx.drawImage(game.hero, 200, 200);
}
game.hero = new Image();
game.hero.src = "hero.png";
game.hero.onload = game.draw;
And this doesn't work:
var game = {};
game.canvas = document.getElementById('canvas');
game.ctx = game.canvas.getContext("2d");
game.hero = new Image();
game.hero.src = "hero.png";
game.hero.onload = game.draw;
game.draw = function(){
game.ctx.drawImage(game.hero, 200, 200);
}
Nothing appears. No error in the console. Why???
Thanks!
You can call functions before define them only with this syntax:
function draw()
{
//COde here
}
By writting
game.draw = function(){};
You define a method to your object, you don't define a JavaScript function: that's why you can't call it before define it :)
In your second hunk of code, you're defining
game.hero.onload = game.draw;
When game.draw is undefined, so your onload is undefined and nothing happens when the image is done loading.
game.draw is not set when you assign it in your second example, you are copying an undefined value into hero.onload.
One workaround is to wrap the function you want to call in an anonymous function and call yours from there:
game.hero.onload = function(){ game.draw(); };
Have in mind that if hero loads before the definition of game.draw is finished, this code will fail.
function find_image_dm(imgsrc){
window.imgwidth = "";
var img = new Image();
img.onload = function test() {
var width = this.width;
var height = this.height;
if (height > 300) {
var x = height/300;
window.imgwidth = Math.ceil(width/x);
}
}
img.src = imgsrc;
return(window.imgwidth);
}
I want to return window.imgwidth so i can use it else where but if I try to alert it or anything like that it shows up blank. How do I return its value. window.imgwidth is my attempt to create a global variable. I do not need the width of the window
The problem with your attempt is that the onload callback executes asynchronously. Because of this, you cannot reliably return anything since the onload callback is executed at some future time. Instead of thinking in terms of return values, think about passing a callback function parameter to your find_image_dm function:
function find_image_dm(imgsrc, callback)
{
var img = new Image();
img.onload = function()
{
callback(this.width, this.height);
};
img.src = imgsrc;
}
Which you'd use like this:
function onReturnSize(w, h)
{
console.log('image width is', w);
console.log('image height is', h);
}
find_image_dm('http://placekitten.com/2200/1200', onReturnSize);
Demo: http://jsfiddle.net/mattball/pTKW4/
The problem i think is when you execute find_img_dm, you are trying to return the width value immediately. This won't work because the img.onload most likely won't be fired before the function finishes executing.
What you would need to do is in the onload event handler, put a call to another function, passing in the width value, and have that function do what you need to do when the image is loaded. Then you will know that at that point, the image is loaded and the width value is available.
OK try this. You have to return the value of window.imgwidth within the function test:
function find_image_dm(imgsrc){
window.imgwidth = "";
var img = new Image();
img.onload = function test() {
var width =this.width
var height= this.height;
if(height > 300){
var x = height/300;
console.log(Math.ceil(width/x));
window.imgwidth = Math.ceil(width/x);
console.log(window.imgwidth);
alert (window.imgwidth);
return window.imgwidth;
}
}
img.src = imgsrc;
}
Ok. Another stab at this, #Tom.
There may be two issues here, from what I can see.
There will be no calculation of window.imgwidth if your if statement returns false, as this appears to be the only place you are calculating it.
The window.imgwidth is being calculated in function test() but not returned.
Looking at your code it seems the issue is you aren't technically loading the image before trying to get the height because you are setting the src after the the onload call.
If you are using jQuery you should try and leverage it.
function find_image(imgsrc) {
var imgWidth,
img = $('<img/>').attr('src', imgsrc).load(function() {
var $img = $(this),
height = $img.height(),
width = $img.width();
if (height > 300) {
var x = height/300;
imgWidth = Math.ceil(width/x);
}
});
return imgWidth;
}
Ok... I went through your code and found a problem with your script. You are using image.onload function, so when u load image source, image loads and then only dimension are available and stored on the window.imgwidth.
All goes fine, but problem is, when u call the function, it takes time to load the u image, so if you try to get the window.imgwidth, as the resource is still loading you wont get the window.imgwidth. Return value also will not work as codes do not wait till the image loads so although the image is loading the code block execution will be completed so you wont have any data in window.imgwidth.
If you try to alert window.imgwidth after certain duration when image loading is completed then you can see that window.imgwidth has the value. And also you have the if block which stores the data in window.imgwidth only when the height more than 300 but you dont have else block so you need to implement that too as a fallback :) .
Let me know if i said it hard way :-D
I have this piece of code (The sCtx is a canvas context and the button is in a tight draw loop):
function Button(src, onClick)
{
this.loaded = false;
this.image = new Image();
this.image.src = src;
this.onClick = onClick;
}
Button.prototype.draw = function()
{
if(!this.image.complete)
return;
var theImg = this.image;
console.log(theImg);
sCtx.drawImage(theImg);
}
When I run the code (in Chrome) I get this output:
<img src="img/btnStart.png">
Uncaught TypeError: Type error
Can someone tell me what I am doing wrong? I have looked at many examples and it seems like this should work.
I believe you need x/y coordinates in order to tell the canvas context where to draw:
sCtx.drawImage(theImg,0,0);