28

When I set the src of an image object, it will trigger an onload function. How can I add parameters to it?

x = 1;
y = 2;
imageObj = new Image();
imageObj.src = ".....";
imageObj.onload = function() {
    context.drawImage(imageObj, x, y);
};
x = 3;
y = 4;

In here, I want to use the x and y values that were set at the time I set the src of the image (i.e. 1 and 2). In the code above, by the time the onload function would finish, x and y could be 3 and 4.

Is there a way I can pass values into the onload function, or will it automatically use 1, and 2?

Thanks

omega
  • 40,311
  • 81
  • 251
  • 474
  • 2
    another way: imageObj.coords={x:x,y:y}; ... drawImage(imageObj, imageObj.coords.x, imageObj.coords.y); – dandavis Jul 10 '13 at 18:54
  • 1
    COMP SCI TECHIE STUFF: The problem above is that the onload function is referring to the GLOBAL variables x and y... and will use the then-current value when executed. To properly use a closure -- and have the variable values "saved" from creation time (of the closure) and used when executed -- you need to use the `var` declaration to give them a scope INSIDE the closure. (This implicitly happens if the variables are formal parameters to a function, which is why the closure-based answers here "work".) – Dan H Aug 13 '15 at 15:44

4 Answers4

46

All the other answers are some version of "make a closure". OK, that works. I think closures are cool, and languages that support them are cool...

However: there is a much cleaner way to do this, IMO. Simply use the image object to store what you need, and access it in the load handler via "this":

imageObj = new Image();
imageObj.x = 1;
imageObj.y = 2;
imageObj.onload = function() {
    context.drawImage(this, this.x, this.y);
};
imageObj.src = ".....";

This is a very general technique, and I use it all the time in many objects in the DOM. (I especially use it when I have, say, four buttons and I want them to all share an "onclick" handler; I have the handler pull a bit of custom data out of the button to do THAT button's particular action.)

One warning: you have to be careful not to use a property of the object that the object class itself has a special meaning or use. (For example: you can't use imageObj.src for any old custom use; you have to leave it for the source URL.) But, in the general case, how are you to know how a given object uses all its properties? Strictly speaking, you can't. So to make this approach as safe as possible:

  • Wrap up all your custom data in a single object
  • Assign that object to a property that is unusual/unlikely to be used by the object itself.

In that regard, using "x" and "y" are a little risky as some Javascript implementation in some browser may use those properties when dealing with the Image object. But this is probably safe:

imageObj = new Image();
imageObj.myCustomData = {x: 1, y: 2};
imageObj.onload = function() {
    context.drawImage(this, this.myCustomData.x, this.myCustomData.y);
};
imageObj.src = ".....";

Another advantage to this approach: it can save a lot of memory if you are creating a lot of a given object -- because you can now share a single instance of the onload handler. Consider this, using closures:

// closure based solution -- creates 1000 anonymous functions for "onload"
for (var i=0; i<1000; i++) {
   var imageObj = new Image();
   var x = i*20;
   var y = i*10;
   imageObj.onload = function() {
       context.drawImage(imageObj, x, y);
   };
   imageObj.src = ".....";
}

Compare to shared-onload function, with your custom data tucked away in the Image object:

// custom data in the object -- creates A SINGLE "onload" function
function myImageOnload () {
   context.drawImage(this, this.myCustomData.x, this.myCustomData.y);
}
for (var i=0; i<1000; i++) {
   imageObj = new Image();
   imageObj.myCustomData = {x: i*20, y: i*10};
   imageObj.onload = myImageOnload;
   imageObj.src = ".....";
}

Much memory saved and may run a skosh faster since you aren't creating all those anonymous functions. (In this example, the onload function is a one-liner.... but I've had 100-line onload functions, and a 1000 of them would surely be considered spending a lot of memory for no good reason.)

UPDATE: See use of 'data-*' attribute for a standard (and "standards approved") way to do this, in lieu of my ad-hoc suggestion to use myCustomData.

Dan H
  • 14,044
  • 6
  • 39
  • 32
  • 4
    Wow. It's amazing how many years i didn't think about the object solution. It's lovely Dan, thx – Tom Smykowski Jun 16 '16 at 09:15
  • cool! instead of `imageObj.myCustomData`, I did `imageObj.this = this`, then called with `this.this`. it was easier than hard-coding the fields – Jason May 01 '17 at 05:31
  • actually put `ths = this.this;` in `onload` function, so could call with `ths.x` – Jason May 01 '17 at 05:37
  • imageObj.x = 1; makes error that x is read-only – Markus Feb 21 '23 at 17:39
  • @Markus: the access I propose may have been restricted in later versions of JavaScript/ECMAScript. Try using the 'dataset' attribute: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset – Dan H Feb 22 '23 at 17:33
12

Make a private scope closure that will store x & y values:

imageObj.onload = (function(x,y){
        return function() {
            context.drawImage(imageObj, x, y);
    };
})(x,y);
gdoron
  • 147,333
  • 58
  • 291
  • 367
10

Make a small function that handles it. Local variables will hold the correct scope.

function loadImage( src, x, y) {

    var imageObj = new Image();
    imageObj.src = src;
    imageObj.onload = function() {
        context.drawImage(imageObj, x, y);
    };

}

var x = 1,
    y = 2;

loadImage("foo.png", x, y);
x = 3;
y = 4;
epascarello
  • 204,599
  • 20
  • 195
  • 236
  • I tried the inline answer below yours first (https://stackoverflow.com/a/17578340/1450077) but it was still behaving like it wasn't capturing local variables in scope, but this method solved it. (It was a copy and paste of my inline function code). I'd love to know why this worked, but the anonymous wasn't if you can share? – Fluffeh Jun 10 '23 at 11:26
6

You could use an anonymous function

x = 1;
y = 2;
(function(xValue, yValue){
    imageObj = new Image();
    imageObj.src = ".....";
    imageObj.onload = function() {
        context.drawImage(imageObj, xValue, yValue);
    };
})(x,y);
x = 3;
y = 4;  
Claudio Redi
  • 67,454
  • 15
  • 130
  • 155