Variable values aren't available for use after image.onload? - javascript

I'm using Canvas to perform a couple of things on an image which is loaded/drawn using image.onload & context.drawImage combo. I'm calculating the bounding size for scaling the images using a simple function which returns the values. I need those values for use at a later point in my code, but no matter what I do, I'm not able to assign the values to a variable. I'm also not able to access my Canvas's styleheight/stylewidth attributes after I assign it the calculated dimensions.
Here's a pseudos ample of my code
$(document).ready(function(){
//Canvas access, context reference etc here.
//Since I'm assigning styles to the canvas on the fly, the canvas has no ht/wdt yet
var dimes = '';
var image = new Image();
image.onload = function(){
//Apply original image height/width as canvas height/width 'attributes'
//(so that I can save the original sized image)
//Check if the image is larger than the parent container
//Calculate bounds if not
//Apply calculated dimensions as 'style' height/width to the canvas, so that the image fits
dimes = scaleImage(...);
//Works!
console.log(dimes);
//Rest all code
}
image.src = '...';
//Blank!!!
console.log(dimes);
//These all blank as well!!!
jQuery('#mycanvas').height() / width() / css('height') / css('width');
document.getElementById(canvas).style.height / .style.width / height / width;
});
I need to access the calculated dimensions for a 'reset' kind of function, that resets my canvas with the drawn image to the calculated size.

As #apsillers noted, the console.log(dimes) code is being executed after you simply define the image.onload() event handler.
If you want to access dimes outside of image.onload(), you'll need to ensure it's being executed after the image loads... e.g. as a response to a button click.
Put the var dimes = ""; before the $(document).ready() to make it a global variable.
Then if you need to access dimes in an event handler, it's ready for you:
$(document).ready(function() {
var image = new Image();
var dimes = "";
image.onload = function() {
dimes = scaleImage(...);
};
$(button).click(function() {
if (dimes === "") {
// image is not yet loaded
} else {
console.log(dimes);
}
});
});
Of course, dimes will now only be accessible inside this first $(document).ready() event handler. If you add another one (which you can certainly do in jQuery), you'll need to use the $.fn.data() jQuery object method to store dimes:
$(document).ready(function() {
var image;
$(document).data("dimes", ""); // initializes it
image.onload = function() {
$(document).data("dimes", scaleImage(...));
};
});
// some other code
$(document).ready(function() {
$("#myButton").click(function() {
var dimes = $(document).data("dimes");
if (dimes === "") {
// image not yet loaded
} else {
console.log(dimes);
}
});
});

Your img.onload function can run only after the JavaScript execution thread stops being busy, i.e., after your ready function completes. Thus, your console.log(dimes) call is running before your onload function.
Put any code that needs to use dimes inside of the onload function. Otherwise, the code the needs to use dimes might run before the onload handler fires.

http://jsfiddle.net/KxTny/1/
$(document).ready(function(){
var dimes = 0;
var width = 20;
var height = 30;
pass(dimes, width, height);
});​
function pass(dimes, width, height) {
alert(dimes);
alert(height);
alert(width);
}

Related

Scaling HTML5 Canvas Image

Building a web page on which I am trying to set an image as the background of the main canvas. The actual image is 1600x805 and I am trying to code the application so that it will scale the image either up or down, according to the dimensions of the user's screen. In Prime.js I have an object that sets the properties of the application's canvas element located in index.html. Here is the code for that object:
function Prime(w,h){
if(!(function(){
return Modernizr.canvas;
})){ alert('Error'); return false; };
this.context = null;
this.self = this;
this.globalCanvasMain.w = w;
this.globalCanvasMain.h = h;
this.globalCanvasMain.set(this.self);
this.background.setBg();
}
Prime.prototype = {
constructor: Prime,
self: this,
globalCanvasMain: {
x: 0,
y: 0,
set: function(ref){
ref.context = document.getElementById('mainCanvas').getContext('2d');
$("#mainCanvas").parent().css('position', 'relative');
$("#mainCanvas").css({left: this.x, top: this.y, position: 'absolute'});
$("#mainCanvas").width(this.w).height(this.h);
}
},
background: {
bg: null,
setBg: function(){
this.bg = new Image();
this.bg.src = 'res/background.jpg';
}
},
drawAll: function(){
this.context.drawImage(this.background.bg, 0,0, this.background.bg.width,this.background.bg.height,
this.globalCanvasMain.x,this.globalCanvasMain.y, this.globalCanvasMain.w,this.globalCanvasMain.h);
}
};
The primary interface through which external objects like this one will interact with the elements in index.html is home.js. Here's what happens there:
$(document).ready(function(){
var prime = new Prime(window.innerWidth,window.innerHeight);
setInterval(prime.drawAll(), 25);
});
For some reason, my call to the context's drawImage function clips only the top left corner from the image and scales it up to the size of the user's screen. Why can I not see the rest of the image?
The problem is that the image has probably not finished loading by the time you call setInterval. If the image is not properly loaded and decoded then drawImage will abort its operation:
If the image isn't yet fully decoded, then nothing is drawn
You need to make sure the image has loaded before attempting to draw it. Do this using the image's onload handler. This operation is asynchronous so it means you also need to deal with either a callback (or a promise):
In the background object you need to supply a callback for the image loading, for example:
...
background: {
bg: null,
setBg: function(callback) {
this.bg = new Image();
this.bg.onload = callback; // supply onload handler before src
this.bg.src = 'res/background.jpg';
}
},
...
Now when the background is set wait for the callback before continue to drawAll() (though, you never seem to set a background which means drawImage tries to draw null):
$(document).ready(function(){
var prime = new Prime(window.innerWidth, window.innerHeight);
// supply a callback function reference:
prime.background.setBg(callbackImageSet);
// image has loaded, now you can draw the background:
function callbackImageSet() {
setInterval(function() {
prime.drawAll();
}, 25);
};
If you want to draw the complete image scaled to fit the canvas you can simplify the call, just supply the new size (and this.globalCanvasMain.x/y doesn't seem to be defined? ):
drawAll: function(){
this.context.drawImage(this.background.bg, 0,0,
this.globalCanvasMain.w,
this.globalCanvasMain.h);
}
I would recommend you to use requestAnimationFrame to draw the image as this will sync with the monitor update.
Also remember to provide callbacks for onerror/onabort on the image object.
There is a problem with the setInterval function. You are not providing proper function reference. The code
setInterval(prime.drawAll(), 25);
execute prime.drawAll only once, and as the result only little part of the image which is being loaded at this moment, is rendered.
Correct code should be:
$(document).ready(function(){
var prime = new Prime(window.innerWidth, window.innerHeight);
setInterval(function() {
prime.drawAll();
}, 25);
});

jQuery and Canvas loading behaviour

After being a long time lurker, this is my first post here! I've been RTFMing and searching everywhere for an answer to this question to no avail. I will try to be as informative as I can, hope you could help me.
This code is for my personal webpage.
I am trying to implement some sort of a modern click-map using HTML5 and jQuery.
In the website you would see the main image and a hidden canvas with the same size at the same coordinates with this picture drawn into it.
When the mouse hovers the main picture, it read the mouse pixel data (array of r,g,b,alpha) from the image drawn onto the canvas. When it sees the pixel color is black (in my case I only check the RED value, which in a black pixel would be 0) it knows the activate the relevant button.
(Originally, I got the idea from this article)
The reason I chose this method, is for the page to be responsive and dynamically change to fit different monitors and mobile devices. To achieve this, I call the DrawCanvas function every time the screen is re-sized, to redraw the canvas with the new dimensions.
Generally, this works OK. The thing is ,there seems to be an inconsistent behavior in Chrome and IE(9). When I initially open the page, I sometimes get no pixel data (0,0,0,0), until i re-size the browser. At first I figured there's some loading issues that are making this happen so I tried to hack it with setTimeout, it still doesn't work. I also tried to trigger the re-size event and call the drawCanvas function at document.ready, still didn't work.
What's bothering me is most, are the inconsistencies. Sometimes it works, sometimes is doesn't. Generally, it is more stable in chrome than in IE(9).
Here is the deprecated code:
<script type="text/javascript">
$(document).ready(function(){setTimeout(function() {
// Get main image object
var mapWrapper = document.getElementById('map_wrapper').getElementsByTagName('img').item(0);
// Create a hidden canvas the same size as the main image and append it to main div
var canvas = document.createElement('canvas');
canvas.height = mapWrapper.clientHeight;
canvas.width = mapWrapper.clientWidth;
canvas.fillStyle = 'rgb(255,255,255)';
canvas.style.display = 'none';
canvas.id = 'hiddencvs';
$('#map_wrapper').append(canvas);
// Draw the buttons image into the canvas
drawCanvas(null);
$("#map_wrapper").mousemove(function(e){
var canvas = document.getElementById('hiddencvs');
var context = canvas.getContext('2d');
var pos = findPos(this);
var x = e.pageX - pos.x;
var y = e.pageY - pos.y;
// Get pixel information array (red, green, blue, alpha)
var pixel = context.getImageData(x,y,1,1).data;
var red = pixel[0];
var main_img = document.getElementById('map_wrapper').getElementsByTagName('img').item(0);
if (red == 0)
{
...
}
else {
...
}
});
},3000);}); // End DOM Ready
function drawCanvas(e)
{
// Get context of hidden convas and set size according to main image
var cvs = document.getElementById('hiddencvs');
var ctx = cvs.getContext('2d');
var mapWrapper = document.getElementById('map_wrapper').getElementsByTagName('img').item(0);
cvs.width = mapWrapper.clientWidth;
cvs.height = mapWrapper.clientHeight;
// Create img element for buttons image
var img = document.createElement("img");
img.src = "img/main-page-buttons.png";
// Draw buttons image inside hidden canvas, strech it to canvas size
ctx.drawImage(img, 0, 0,cvs.width,cvs.height);
}
$(window).resize(function(e){
drawCanvas(e);
}
);
function findPos(obj)
{
...
}
</script>
I'd appreciate any help!
Thanks!
Ron.
You don't wait for the image to be loaded so, depending on the cache, you may draw an image or not in the canvas.
You should do this :
$(function(){
var img = document.createElement("img");
img.onload = function() {
var mapWrapper = document.getElementById('map_wrapper').getElementsByTagName('img').item(0);
...
// your whole code here !
...
}
img.src = "img/main-page-buttons.png";
});

How to do a process after completion of another one in JavaScript

I want to add an image by Javascript, then calculating the html element width as
window.onload=function(){
document.getElementById('x').addEventListener('click', function(e){
var el = document.getElementById('xx');
el.innerHTML = '<img src="img.jpg" />';
var width = el.offsetWidth;
.....
}, false);
}
but since JavaScript conduct all processes simultaneously, I will get the width of the element before loading the image. How can I make sure that the image has been loaded into the content; then calculating the element width?
UPDATE: Thanks for the answers, but I think there is a misunderstanding. img src="img.jpg" /> does not exist in the DOM document. It will be added later by Javascript. Then, when trying to catch the element by Id, it is not there probably.
You can give the img an ID and do the following :-
var heavyImage = document.getElementById("my-img");//assuming your img ID is my-img
heavyImage.onload = function(){
//your code after image is fully loaded
}
window.onload=function(){
document.getElementById('x').addEventListener('click', function(e){
var el = document.getElementById('xx');
var img = new Image();//dynamically create image
img.src = "img.jpg";//set the src
img.alt = "alt";
el.appendChild(img);//append the image to the el
img.onload = function(){
var width = el.offsetWidth;
}
}, false);
}
This is untested, but if you add the image to the DOM, set an onload/load event-handler and then assign the src of the image, the event-handling should fire (once it's loaded) and allow you to find the width.
This is imperfect, though, since if the image is loaded from the browser's cache the onload/load event may not fire at all (particularly in Chromium/Chrome, I believe, though this is from memory of a bug that may, or may not, have since been fixed).
For the chrome bug you can use the following:-
var BLANK = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==';//create a blank source
var tImg = document.getElementById("my-img");//get the image
var origSrc = tImg.src;//get the original src
tImg.src = BLANK;//change the img src to blank.
tImg.src = origSrc;//Change it back to original src. This will lead the chrome to load the image again.
tImg.onload= function(){
//your code after the image load
}
You can use a library called PreloadJS or you can try something like this:
//Somewhere in your document loading:
loadImage(yourImage, callbackOnComplete);
function loadImage(image, callbackOnComplete){
var self = this;
if(!image.complete)
window.content.setTimeout(
function() { self.loadImage(image, callbackOnComplete)}
,1000);
else callbackOnComplete();
}
I did this when I worked with images base64 which delay on loading.

Accessing Image Width Outside of onload with Javascript

I am trying to use a call back to determine if an images width is correct. It seems that when using an onload that it is impossible to do a return true or false.
Should this callback work to alleviate that issue? It seems that the onload event is never firing.
validateImageSize(function(imageWidth){
alert(imageWidth);
});
function validateImageSize(callback) {
imageFile = document.getElementById("filePath").files[0];
imageFileUrl = window.URL.createObjectURL(imageFile);
uploadedImage = new Image();
uploadedImage.onload = function(){
var imageWidth = uploadedImage.width;
callback(imageWidth);
};
uploadedImage.src = imageFileUrl;
};

JavaScript/jQuery: Running DOM commands *after* img.onload commands

I need to get the width & height of a CSS background image and inject it into document.ready javascript. Something like:
$(document).ready(function(){
img = new Image();
img.src = "images/tester.jpg";
$('body').css('background', 'url(' + img.src + ')');
$('body').css('background-size', img.width + 'px' + img.height + 'px');
});
The problem is, the image width and height aren't loaded in at the time of document.ready, so the values are blank. (They're accessible from console, but not before).
img.onload = function() { ... } retrieves the width and height, but DOM $(element) calls aren't accessible from within img.onload.
In short, I'm a little rusty on my javascript, and can't figure out how to sync image params into the DOM. Any help appreciated
EDIT: jQuery version is 1.4.4, cannot be updated.
You could use $.holdReady() to prevent jQuery's ready method from firing until all of your images have loaded. At that point, their width's and height's would be immediately available.
Star by calling $.holdReady(true). Within the onload method of each image, check to see how many images have been loaded all together, and if that matches the number of expected images, you can $.holdReady(false) to fire the ready event.
If you don't have access to $.holdReady(), you could simply wait to spit out your HTML until all of the images have loaded by still calling a function:
var images = { 'loaded': 0,
'images': [
"http://placekitten.com/500/500",
"http://placekitten.com/450/450",
"http://placekitten.com/400/400",
"http://placekitten.com/350/340",
"http://placekitten.com/300/300",
"http://placekitten.com/250/250",
"http://placekitten.com/200/200",
"http://placekitten.com/150/150",
"http://placekitten.com/100/100"
]};
function outputMarkup() {
if ( ++images.loaded === images.images.length ) {
/* Draw HTML with Image Widths */
}
}
for ( var i = 0; i < images.images.length; i++ ) {
var img = new Image();
img.onload = function(){
outputMarkup();
};
img.src = images.images[i];
}

Categories

Resources