canvas drawImage and scope issue - javascript

I'm writing a small javascript class that I'm trying to use to create an image via canvas
I keep running into an issue where when I run the update function to redraw the canvas, some properties are undefined when they should be an instance of an image
To better explain what I mean, this code is what I have:
this.setHabboLeft = function(src)
{
var callback = this.update;
this.habboLeft = getImage(src, callback);
}
var getImage = function(src, callback)
{
var img = new Image;
if(typeof callback == 'function')
{
img.onload = callback;
}
img.src = src;
return img;
}
this.update = function()
{
console.log('updating...', this.background, this.habboLeft);
...
}
I am using setHabboLeft that sets an image from an external URL, then once that is loaded, it will run this.update()
I think the problem is with the onload callback in getImage. The console.log should return the image object but tells me it's undefined
EDIT for #gus27
Note, the image is on a different domain
I call the functions like:
// set background of lovelock
lovelockCanvas.setBackground($(self).data('picture'));
// create logged in user habbo
var habboUrl = habboCreator.generateUrl({
habbo_username: '{{ Auth::user()->habbo_username }}'
});
lovelockCanvas.setHabboLeft(habboUrl);
EDIT 2
Function being called
http://pastebin.com/ZRut9gyD
full js class
http://pastebin.com/WjRUkj4X
FIXED:
Instead of my update using this to draw everything, I created a new function and passed this as a parameter
See pastebin: http://pastebin.com/BTNigqLS

The problem is in the update function which is called by the img. When update is called by the img (by it's onload callback) the this variable in the update function refers to the img.
The documentation for this states:
Inside a function, the value of this depends on how the function is
called.
You can try something like that:
this.setHabboLeft = function(src)
{
var callback = this.update;
this.habboLeft = getImage(src, callback, this);
}
var getImage = function(src, callback, obj)
{
var img = new Image;
if(typeof callback == 'function')
{
img.onload = function(){
callback(obj);
};
}
img.src = src;
return img;
}
this.update = function(obj)
{
console.log(this); // this is here the img, not the lovelockCanvas
console.log('updating...', obj.background, obj.habboLeft);
}

Function.bind(objRef)
All functions are a type of object called Function MDN Function object. They come with some methods and properties, one of which is called bind.
Bind creates a new copy of the function binding its keyword (this) to the function.
Thus to solve the problem of an event overwriting the functions binding you simple do the binding yourself to ensure it is not overwritten by the native event handler.
this.setHabboLeft = function(src){
// create a new function bound to this
this.habboLeft = getImage(src, this.update.bind(this));
}
var getImage = function(src, callback){
var img = new Image;
img.onload = typeof callback == 'function' ? callback : undefined;
img.src = src;
return img;
}
this.update = function() {
console.log('updating...', this.background, this.habboLeft);
...
}
Additionally you don't have or add more intermediate steps to keep hold of the event object.
this.setHabboLeft = function(src){
// create a new update function bound to this
// only reference the image if it loads
getImage(src, this.update.bind(this));
}
var getImage = function(src, callback){
var img = new Image;
img.onload = typeof callback == 'function' ? callback : undefined;
img.src = src;
}
this.update = function(event){
// image loaded so can be referenced
this.habboLeft = event.target; // the image object
}

Related

How to globally access properties of an image loaded in with JavaScript

So I have loaded in an image and can access the properties within this function locally such as ball.height and width, but I need to use these properties in other functions to do calculations with. How would I access these properties since they are only locally defined currently. I can't set it as global (or at least don't know how) since in order to get the image in the first place I have to load it in with the function.
function drawBall() {
var ball = new Image();
$(ball).on('load', function() {
ctx.drawImage(ball, xBall, yBall);
console.log(ball.height);
});
ball.src = 'ball.png';
}
function ballHit() {
// For example calculate if ball.height = canvas.height
}
Since the image load is a asynchronous task, you can use a callbackļ¼š
function drawBall(callback) {
var ball = new Image();
$(ball).on('load', function() {
ctx.drawImage(ball, xBall, yBall);
console.log(ball.height);
callback(ball.height,ball.width);
});
ball.src = 'ball.png';
}
drawBall(ballHit);
Create a global variable globalBall and once the image is loaded you can assign the image from the function to the global object.
var globalBall = new Image();
function drawBall() {
var ball = new Image();
$(ball).on('load', function() {
ctx.drawImage(ball, xBall, yBall);
console.log(ball.height);
});
ball.src = 'ball.png';
globalBall = ball;
}
function ballHit() {
if(globalball.height == canvas.height)
{
// Do your stuff
}
}

get actual width/height of an image without loading the image into the page with javascript

I was wondering if it was possible to get the width and height of an image without putting an image into page, i.e without creating an image tag that displays it.
trying to make a sprite class using this method.
function Sprite(src,frames) {
// Sprite class
this.img = new Image();
this.img.src = src;
this.frames = frames;
this.cframe = 1;
this.setDim = function() {
this.fullWidth = this.img.width;
this.fullHeight = this.img.height;
}
this.img.onload = this.setDim();
console.log(this.fullWidth);
return this;
}
however this.fullWidth returns undefined
and the below that ignores the onload returns 0
function Sprite(src,frames) {
// Sprite class
this.img = new Image();
this.img.src = src;
this.frames = frames;
this.cframe = 1;
this.fullWidth = this.img.width;
this.fullHeight;
this.setDim = function() {
this.fullWidth = this.img.naturalWidth;
this.fullHeight = this.img.height;
console.log(this.fullWidth)
}
console.log(this.fullWidth)
//this.img.onload = this.setDim();
return this;
}
I don't really want to use Jquery for this.
I have also tried this.img.natrualWidth (as you can see in the example above)
it also returns 0
Any advice would be great,
Thanks
Updated this to match #vihan1086 answer
function Sprite(src,frames) {
// Sprite class
this.img = new Image();
this.img.src = src;
this.frames = frames;
this.cframe = 1;
var self = this;
self.loaded = function () {};
this.setDim = function() {
self.fullWidth = this.width;
self.fullHeight = this.height;
self.frameWidth = this.width / self.frames;
self.frameHeight = this.height;
self.loaded.apply(self, []);
}
this.loaded = function() {
return this;
}
this.img.onload = this.setDim;
}
then use
sprite = new Sprite(sprite,5);
sprite.loaded = function() {
console.log(sprite.fullWidth);
}
Problem
var img = new Image();
img.src = '/my/src/to/file';
//this refers to the current function at this point
img.onload = function () {
//this is 'img' at this point not the function
}
Solution
this is not in scope so you would add:
var self = this;//Self and this are referring to the same thing, the function
img.onload = function () {
//this refers to image but self still refers to the function's this
self.width = this.width;
self.height = this.height;
}
console.log(this.width);//logs width
console.log(this.height);//logs height
This leaves async problems which can be solved using two methods
A
this.img.onload = this.setDim; //Noticed the dropped ()
B
self.loaded = function () {};
this.setDim = function () {
//...
self.loaded.apply(self, []);
}
then
var sprite = new Sprite(...);
sprite.loaded = function () {
console.log(this.fullHeight);
}
Explanation
img.onload() changes the scope of the code, resulting in your this referring to img. Now the weird part. We created a variable self which refers to this, this allows us to refer to this in a different scope by using self.
Other
img.onload is "async" which means it won't follow along with the rest of the code. This means the console.log() has run, but the img.onload hasn't. Be careful when working when this type of code (I wrote a few solutions in the update). You should wait until img.onload is finished before checking the values. I've worked on something like this before and I'll see if I can find what I did to address all these issues; if I can, I'll update this answer.
UPDATE: I wouldn't run the setDim function at first and let the user run it setDim() -> setDim. If you wish to load the dimensions at first, put a load function to your Sprite() which is run when the dimensions are retrieved.
In javascript the statements are executed asynchronously. To know more about this read this excellent article Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
In your case as #Juhana mentioned passing reference should get the issue resolved

Mootools "Cannot read property 'call" of undefined"

I've got a website that is using MooTools. I've been getting the following errror, but can't figure out what is causing it. I've had very little luck tracing it out. Does anyone know what this might be? I haven't been able to find anything on the web about it.
Uncaught TypeError: Cannot read property 'call' of undefined mootools-core.js:4497
condition mootools-core.js:4497
defn mootools-core.js:4511
Stackoverflow has this question, but it does not relate as far as I can tell. Ideas?
Update
Having looked at this a bit more, the cause of the error is still mysterious. The offending line in my code is an addEvent call:
window.addEvent('load', preloader(preload));
The variable preload is an array of image urls. And the callback preloader is a method that preloads the images specified in preload. Here's the preloader method:
/**
* Event callback that preloads images
*/
function preloader(images) {
var img;
if ( images ) {
for (var i=0; i<images.length; i++) {
img = new Image();
img.src = images[i];
}
}
}
The line of mootools code specified by the error is this:
addEvent: function(type, fn){
var events = this.retrieve('events', {});
if (!events[type]) events[type] = {keys: [], values: []};
if (events[type].keys.contains(fn)) return this;
events[type].keys.push(fn);
var realType = type,
custom = Element.Events[type],
condition = fn,
self = this;
if (custom){
if (custom.onAdd) custom.onAdd.call(this, fn, type);
if (custom.condition){
condition = function(event){
//error here--> if (custom.condition.call(this, event, type)) return fn.call(this, event);
return true;
};
}
if (custom.base) realType = Function.from(custom.base).call(this, type);
}
var defn = function(){
return fn.call(self);
};
var nativeEvent = Element.NativeEvents[realType];
if (nativeEvent){
if (nativeEvent == 2){
defn = function(event){
event = new DOMEvent(event, self.getWindow());
if (condition.call(self, event) === false) event.stop();
};
}
this.addListener(realType, defn, arguments[2]);
}
events[type].values.push(defn);
return this;
},
Er. you are not passing a function as callback.
this:
window.addEvent('load', preloader(preload));
/**
* Event callback that preloads images
*/
function preloader(images) {
var img;
if ( images ) {
for (var i=0; i<images.length; i++) {
img = new Image();
img.src = images[i];
}
}
}
it will essentially invoke the preloader function immediately, not onload - and it will try to bind the event to the result of the preloader function, which does not return anything at all.
when the interpreter sees preloader(preload), it just runs it straight away. you can return a function or better yet, rewrite to:
window.addEvent('load', function(){ preloader(preload); });
// or even
window.addEvent('load', preloader.bind(this, preload));
Running example:
var imagesArray = new Array(50).join(',').split(',');
imagesArray = imagesArray.map(function(el, i){
return 'http://dummyimage.com/600x400/000/' + (255 - i) + '?' + +new Date();
});
function preloader(images) {
var img;
if ( images ) {
for (var i=0; i<images.length; i++) {
img = new Image();
img.src = images[i];
console.log(img.src);
}
}
}
window.addEvent('load', function(){
preloader(imagesArray);
});
<script src="//cdnjs.cloudflare.com/ajax/libs/mootools/1.5.0/mootools-core-full-nocompat.js"></script>
You can also have a look at my preloader class which gives you greater flexibility over how your images are pre-loaded, as well as progress etc. https://github.com/DimitarChristoff/pre-loader - it will actually wait for the images to download, allow you to choose how they are loaded etc.
mootools-more also has Asset.images you can use.
finally, not sure you want to bind to load event, which will trigger when all assets, including images, have been loaded, you should be able to start at domready instead.
It means, I believe, that mooTools is getting an undefined/malformed parameter in one of your call.
Find out which function is defined at line 4497 of mootools-core.js (can be an object method) and look for each call in your script. Log all parameters/object you're working with, and you'll find your error ;)
Edit
Seeing your code, I think your problem come from the fact that type is not declared inside condition. Try :
if (custom.condition){
condition = function(event,type){
if (custom.condition.call(this, event, type)) return fn.call(this, event);
return true;
};

calling a javascript function without parenthesis

The following renderChat function is used to render a message and an image onto a chat board. Inside the function there is another function
var onComplete = function () {
which does all the work of creating the list element and appending it to the chat list. After the onComplete function, there is only this three lines of code
img.onload = onComplete;
img.onerror = onComplete;
img.src = c.chat.value.media;
Because the var onComplete is a function assigned to a variable, I assumed it had to be called with parenthesis. Thus, when I see this
img.onload = onComplete;
I understand that the function has been assigned to a new variable, but has never been called. Yet, when I use the application, the chat has been rendered by the time we reach img.src = c.chat.value.media;
Can you please explain how my understanding of JavaScript is mistaken and how this function is working?
var renderChat = function (c) {
debug("Rendering chat: key='%s' fingerprint='%s' message='%s' created='%s' imageMd5='%s'",
c.chat.key,
c.chat.value.fingerprint,
c.chat.value.message,
c.chat.value.created,
md5(c.chat.value.media));
var renderFP = c.chat.value.fingerprint;
if (!isMuted(renderFP)) {
var img = new Image();
var onComplete = function () {
// Don't want duplicates and don't want muted messages
if (body.find('li[data-key="' + c.chat.key + '"]').length === 0 &&
!isMuted(renderFP)) {
var li = document.createElement('li');
li.dataset.action = 'chat-message';
li.dataset.key = c.chat.key;
li.dataset.fingerprint = renderFP;
li.appendChild(img);
// This is likely your own fingerprint so you don't mute yourself. Unless you're weird.
if (userId.val() !== renderFP) {
updateNotificationCount();
var btn = document.createElement('button');
btn.textContent = 'mute';
btn.className = 'mute';
li.appendChild(btn);
}
var message = document.createElement('p');
message.textContent = c.chat.value.message;
message.innerHTML = transform(message.innerHTML);
li.appendChild(message);
var createdDate = moment(new Date(c.chat.value.created));
var timestamp = document.createElement('time');
timestamp.setAttribute('datetime', createdDate.toISOString());
timestamp.textContent = createdDate.format('LT');
timestamp.className = 'timestamp';
li.appendChild(timestamp);
var size = addChat.is(":visible") ? addChat[0].getBoundingClientRect().bottom : $(window).innerHeight();
var last = chatList[0].lastChild;
var bottom = last ? last.getBoundingClientRect().bottom : 0;
var follow = bottom < size + 50;
chatList.append(li);
setupWaypoints(li);
debug('Appended chat %s', c.chat.key);
// if scrolled to bottom of window then scroll the new thing into view
// otherwise, you are reading the history... allow user to scroll up.
if (follow) {
var children = chatList.children();
if (children.length > CHAT_LIMIT) {
children.first().remove().waypoint('destroy');
}
li.scrollIntoView();
}
}
};
img.onload = onComplete;
img.onerror = onComplete;
img.src = c.chat.value.media;
}
};
The HTMLImageElement object will cause functions assigned to its onload and onerror properties to be called at the appropriate times (i.e. when the HTTP response is received or the wait for it times out).
The code to do this is built into the browser. The properties (or the addEventListener function in more modern code) are the only ways with which you can interact with that code.
In Javascript you can store functions in variables, this is what you did with onComplete().
The img object will execute a function(so called callback) after successfully loading the image (onload) or if it could not load the image(onerror).
To tell the img object which method to call after those events, you need to give it the method name like this without parenthesis:
img.onload = onComplete;
img.onerror = onComplete;
If you would use parenthesis the function would be executed immediately and img.onload wouldn't contain a reference to a function but the result of onCompleted.
img.onload = onComplete; will assign the function onComplete to the onload handler. THis means, that the function is called, when the vent happens.
img.onload = onComplete(); will assign the result of calling the function onComplete to the onload handler. This means, that the function is called immediately, is expected to return another function (or a string containing valid JS), which in turn will be called when the event happens.

Javascript: every event-handler defined in for-loop is the same, uses last iteration's values

I have trouble understanding the scoping rules in Javascript.
In the example below, I would assume that scope url variable is private in the for-loop. And that the onload-event function would see this private instance.
But things does not seems work like that - the alert will popup with the last url twice.
If somebody can clarify what is going on, I'll be grateful.
<html>
<head>
</head>
<body>
<script type="text/javascript">
var testArray = ["http://g0.gstatic.com/images/icons/onebox/weather_rain-40.png", "http://g0.gstatic.com/images/icons/onebox/weather_scatteredshowers-40.png"];
for (var i=0;i<testArray.length;i++){
var img = new Image();
var url = testArray[i];
img.onload = function(){
alert(url);
}
img.src = url;
}
</script>
</body>
</html>
JavaScript does not have block-scope.
The only way to create new variable scope is in a function.
var testArray = ["http://g0.gstatic.com/images/icons/onebox/weather_rain-40.png", "http://g0.gstatic.com/images/icons/onebox/weather_scatteredshowers-40.png"];
function createImg( url ) {
var img = new Image();
img.onload = function(){
alert(url);
}
img.src = url;
return img;
}
for (var i=0;i<testArray.length;i++){
var img = createImg(testArray[i]);
}
Passing the testArray[i] to a function that creates and returns the new image ensure that the url referenced in the onload handler will be the one that was scoped in the function.
EDIT:
Ultimately, you'd never do this if all you need is access to the url.
You'd just get it from the property of the element via this.
function onloadHandler(){
alert( this.src ); // <--- get the url from the .src property!
}
var testArray = ["http://g0.gstatic.com/images/icons/onebox/weather_rain-40.png", "http://g0.gstatic.com/images/icons/onebox/weather_scatteredshowers-40.png"];
for (var i=0;i<testArray.length;i++){
var img = new Image();
var url = testArray[i];
img.onload = onloadHandler;
img.src = url;
}
This way you're not creating an identical handler function instance in the loop, but rather sharing the same instance, and referencing the element that received the event via this.
Javascript is not block-scoped, and thus requires a new function every time you want a new scope. See the answer by patrick dw.
This is why it is advantageous to use [].map(function(x){...}) or [].forEach(function(x){...}) which are in the javascript standard, since you'll need to define those functions anyway.
var imageArray = urlArray.map(function(url) {
var image = new Image();
image.src = url;
image.onload = function() {
alert(url);
};
return image;
});
Try this :)
var testArray = ["http://g0.gstatic.com/images/icons/onebox/weather_rain-40.png", "http://g0.gstatic.com/images/icons/onebox/weather_scatteredshowers-40.png"];
for (var i=0;i<testArray.length;i++){
var img = new Image();
var url = testArray[i];
img.onload = function(){
alert([img.src, url, i]);
}
img.src = url;
}

Categories