Pass additional parameters to load event JavaScript [duplicate] - javascript

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

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.

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);

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;

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;

Related

Canvas JavaScript HTML --Creating several occurrences from one parent

I am initializing classes like so (Character is a class in my program):
character = new Character();
I want two instances of this class/object, so I tried this:
character2 = new Character();
Yet character2 simply replaces character; therefore, there is only one object. Is it possible to create another instance, or would I need to make another Character class (a lot of code duplication!).
I tried adding a second draw function (named draw2, for the second object), but that didn't help.
You need to move the definition of the Character.prototype.draw method out of the constructor function. Otherwise, each time you create a new Character(), you also override the Character.prototype.draw method.
You also need to replace references to local constructor function variables within the Character.prototype.draw method such as x, y or size with object properties such as this.X, this.Y and this.Size.
Also, you need to make the img a (possibly static) property of your Character.
Object creation in modern JS
Modern JS includes some shortcuts when defining objects. There is no real need to use prototype unless you are creating many instances of the Object. Accessing the prototype adds some overhead when using objects
You can create the object inside the creating function that allows you to define private properties via closure.
Closure creates private properties
function Character() {
var x = 0;
var y = 0;
var size = 25;
var vx = 4;
var vy = 4;
var width = 45;
var height = 45;
var img = new Image();
img.src = 'character.jpg';
var pattern;
// using API = {rather than return { allows you to access the instance of the
// inside this scope without having to use the `this` token
const API = {
get x() { return x },
get y() { return y },
get vx() { return vx },
get vy() { return vy },
get size() { return size },
get width() { return width },
get height() { return height },
set x(v) { x = v },
set y(v) { y = v },
set vx(v) { vx = v },
set vy(v) { vy = v },
set size(v) { size = v },
set width(v) { width = v },
set height(v) { height = v },
draw(ctx) {
ctx.save();
ctx.translate(x, y);
ctx.lineWidth = 2;
ctx.fillStyle = pattern ? pattern = ctx.createPattern(img, "no-repeat") : pattern;
ctx.beginPath();
ctx.moveTo(-size, -size);
ctx.lineTo(-size, size);
ctx.lineTo(size, size);
ctx.lineTo(size, -size);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.restore();
},
}
return API;
}
usage
var char = Character();
var char1 = Character();
// or
var char = new Character();
var char1 = new Character();
Performance considerations
If the object is required in performance code you may want to create more performant setters and getters, or avoid the getter setter overhead and include the properties in the object itself.
function Character() {
var img = new Image();
img.src = 'character.jpg';
var pattern;
// using API = {rather than return { allows you to access the instance of the
// inside this scope without having to use the `this` token
const API = {
x : 0,
y : 0,
size : 25,
vx : 4,
vy : 4,
width : 45,
height : 45,
draw(ctx) {
const size = API.size;
ctx.save();
ctx.translate(API.x, API.y); // Note that I use API.x rather than this.x
ctx.lineWidth = 2;
ctx.fillStyle = pattern ? pattern = ctx.createPattern(img, "no-repeat") : pattern;
ctx.beginPath();
ctx.moveTo(-size, -size);
ctx.lineTo(-size, size);
ctx.lineTo(size, size);
ctx.lineTo(size, -size);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.restore();
},
}
return API;
}
Notes.
I noticed you have way too many comments.
eg
//public property for VX
Object.defineProperty(this, 'width',
and
//function public draw method
Character.prototype.draw = function (ctx) {
//save the ctx
ctx.save();
//set x and y
ctx.translate(x, y);
//set the line width
ctx.lineWidth = 2;
You are stating the obvious in a comment, no machine will read it, no human needs to read it, so why is it there. Comments add noise, and source code noise is dangerous, avoid all unnecessary noise in your code.
You create the pattern each time the draw function is called, this is unneeded overhead. Create it only once.
There is a time when calling the draw function may not work as the image will not have loaded yet. Maybe you should manage images outside the object where you can ensure that media objects are loaded and ready to be used befor you try to use them.
Each instance of Character will load the image, create 100 of them and there will be 100 copies of the same image. This will affect performance and memory negatively.

combine array of images with javascript (with or without canvas)

I would like to create a strip of images and compose a new image, like image = [image0-image1-image2].
We'll use:
images = ['https://upload.wikimedia.org/wikipedia/commons/5/55/Al-Farabi.jpg',
'https://upload.wikimedia.org/wikipedia/commons/e/e1/FullMoon2010.jpg',
'https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/3D_coordinate_system.svg/10000px-3D_coordinate_system.svg.png']
I would like to take external above, and make a collage.
I would like to do it in background.
I learnt that is possible to use a canvas element off the dom; for the sake of watching what I am doing, I will use a canvas element here.
// create an off-screen canvas using document.createElement('canvas')
// here I use a canvas in DOM cause I cannot find a way to displayed the final collage
var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d');
// set its dimension to target size
canvas.width = 1200;
canvas.height = 630;
and found three different behaviors for what I think should give same result. Could you explain me why?
If I manually copy and paste in console code for each image, one at a timeenter code here`
var image = new Image();
// i is the i-th element in images
image.src = images[i];
image.onload = function() {
context.save();
context.drawImage(image, canvas.width * 0.3 * i, 0, canvas.width*0.3, canvas.height);
}
I can see the elements are positioned one aside of the other, like I would like to have.
But If I copy all of three pieces of code at once, either in a loop, I can see only the last image placed in all of the three different positions:
for (var i = images.length; i <= 0; i++) {
var image = new Image();
image.src = images[i];
image.onload = function(){
context.save();
context.drawImage(image, canvas.width*0.3 * i, 0, canvas.width*0.3, canvas.height);
}
}
So I thought, maybe it's a matter of using a callback after image is loaded - I tried the following but nothing happens: canvas stays empty.
// my callback
function addImage(image, position){
image.onload = function(){
context.save();
context.drawImage(image, canvas.width*0.3 * position, 0, canvas.width*0.3, canvas.height);
}
}
function loadImages (images, callback) {
for (var i = images.length-1; i >= 0; i--) {
var image = new Image();
image.src = images[i];
callback(image, i);
}
}
// canvas will stay empty:
loadImages(images, addImage);
Can you help in clarifying the differences in the three parts, and figure out how to combine an array of images in a single one?
Possibly in background, I want to then save the image and post it via ajax.
In your loop example, all the onload functions are sharing the same i and image variables from the loop. But the onload functions are callback functions that get called after the loop completes. Thus, all the onload functions are using the same i and image values from after the loop completed. You need to create a local scope such that each onload function has its own i and image values. For example...
for (var i = 0; i < images.length; i++) {
var image = new Image();
image.src = images[i];
image.onload = function(image, i) {
return function(){
context.drawImage(image, canvas.width*0.3 * i, 0, canvas.width*0.3, canvas.height);
}
}(image, i);
}

Javascript constructor not working

I posted this on gamedev.stackexchange but was referred here so I'll try. I've got this simple menu that is a function, with a mainmenu.prototype.Render to draw it to the screen. Inside the mainmenu function i would like to make an array of objects containing the buttons x, y positions and the .src.
This is my current code that works, so no problem with the function itself:
this.Mainmenu = function() {
}
this.Mainmenu.prototype.Render = function() {
imgPause = new Image();
imgPause.src = 'img/pause.png';
c.drawImage(imgPause, canvas.width - 42, 10);
}
var mainmenu = new self.Mainmenu();
What I would like the final result to look like, but can't get to work (I've included the error in a comment):
this.Mainmenu = function() {
this.button = function(src, X, Y) {
this = new Image(); // Gives error "Invalid left-hand side in assignement"
this.src = src;
this.X = X;
this.Y = Y;
}
this.buttons = [pause = new this.button(src, X, Y)];
}
this.Mainmenu.prototype.Render = function() {
for (i = 0; i < this.buttons.length; i++) {
c.drawImage(this.src, this.X, this.Y);
}
}
var mainmenu = new self.Mainmenu();
But it doesn't work, if anyone can identify where my mistake is it would be appreciated, my patience is about to run out.
Well, your mistake is exactly what your js interpreter says it is - the left side of your assignment is invalid. Namely, you cannot assign this to anything, that's a rule of thumb in all languages that have the this word. The reasoning behind that is obvious - this denotes the current context of the function, the hidden argument of its. If you could overwrite it dynamically, you could alter the behaviour of every single function that is using yours thus the whole program.
How not to use this in this broken way:
this.MainMenu = function() {
this.Button = function(src, X, Y) {
var image = new Image();
image.src = src;
image.X = X;
image.Y = Y;
return image;
}
this.buttons = [pause = new this.Button(src, X, Y)];
}
Also, name your classes with PascalCase (Button, not button) and your variables with camelCase EVERYWHERE (x, not X).
You cannot do this
this.button = function(src, X, Y) {
this = new Image(); // Gives error "Invalid left-hand side in assignement"
}
this represents the current instance of Mainmenu. You cannot override an instance by another
instance.
No sense.

How do I capture the onclick event called in HTML?

So, I have an <img> tag that has an onclick attribute. The onclick calls a function called analyze(this), with this being the image.
The analyze function does some things to the image that aren't entirely relevant, except for the fact that it draws it onto the <canvas> element (using the drawImage function).
But now, I want to also pick the color I just clicked on in the image. I am currently using the method answered here (the answer with 70+ votes, not the chosen one): How do I get the coordinates of a mouse click on a canvas element?
But, I think I might be doing this wrong. I have the image drawn and my functions called (and those all work), but the color picking part isn't being called. I think that this is because I didn't actually capture the event. This is generally how my code looks:
<img onclick="javascript:analyze(this);" />
function analyze(img_elem) {
// This is getting the canvas from the page and the image in it
var canvaselement = document.getElementById('canvas').getContext('2d'),
img = new Image();
img.onload = function () {
canvaselement.drawImage(img, 0, 0, 250, 250);
...
canvaselement.onClick = function () {
var coords = canvaselement.relMouseCoords(event);
pick(img, canvaselement, coords); // pass in coordinates
}
}
img.src = img_elem.src;
}
function relMouseCoords(event) {
var totalOffsetX = 0;
var totalOffsetY = 0;
var canvasX = 0;
var canvasY = 0;
var currentElement = this;
do {
totalOffsetX += currentElement.offsetLeft - currentElement.scrollLeft;
totalOffsetY += currentElement.offsetTop - currentElement.scrollTop;
}
while (currentElement = currentElement.offsetParent)
canvasX = event.pageX - totalOffsetX;
canvasY = event.pageY - totalOffsetY;
return {
x: canvasX,
y: canvasY
}
}
function pick(img, canvaselement, coords) {
var pickedColor = "";
canvaselement.drawImage(img, 0, 0, 250, 250);
xx = coords.x;
yy = coords.y;
var imgData = canvas.getImageData(xx, yy, 1, 1).data;
pickedColor = rgbToHex(imgData);
//alert(pickedColor);
return pickedColor;
}
So, the code never gets to the pick function. I have a feeling that it's because I didn't actually capture the onclick event. I'm also not even sure if this is the right way to get the coordinates on the canvas, I'm just sort of hoping that I even get to that part of the debugging process at this point.
Thanks for your help!
The problem is probably that you're assigning canvaselement to the results of getContext('2d') and not to the element itself, which you will need for the click event binding. Create two variables, one for the DOM element itself and one for the context, something like:
var canvaselement = document.getElementById('canvas'),
canvaselementctx = canvaselement.getContext('2d');
...
canvaselement.onClick = function() {
var coords = canvaselementctx.relMouseCoords(event);
...
}
You have a couple of errors in the code but the reason the code you got from the linked post is that you forgot to include the prototype definition it uses:
HTMLCanvasElement.prototype.relMouseCoords = relMouseCoords;
Now you can call relMouseCoords on the canvas element:
/// event name in lower case
canvaselement.onclick = function () {
var coords = canvaselement.relMouseCoords(event);
//...
However, you will still get problems as you don't use a canvas context for the drawing calls.
function analyze(img_elem) {
// This is getting the canvas from the page and the image in it
var canvaselement = document.getElementById('canvas').getContext('2d'),
/// get context like this
ctx = canvaselement.getContext('2d'),
img = new Image();
img.onload = function () {
/// use context to draw
ctx.drawImage(img, 0, 0, 250, 250);
//...

Multiple Canvas images appearing randomly

I am trying to apply different images to multiple canvas.
The images are passed into a json array that is generated in the code behind and then looped through drawing to there specific canvas.
Now this appears to work on occasion sometimes rendering sometimes not.
So here is the code.
function renderPlayer() {
var imagesList = <%=imageList %>;
var size = imagesList.length;
for(var key in imagesList)
{
var image = imagesList[key][1];
var gridPosition = imagesList[key][0];
var playerCanvas = document.getElementById(gridPosition);
window.context2 = playerCanvas.getContext("2d");
//context2.save();
playerCanvas.setAttribute("width", 97);
var imageObj = new Image();
imageObj.src = image;
context2.drawImage(imageObj, 0, 10, 97, 97);
}
}
The above code places them where I expect them to but no every time the page is rendered.
However the bellow code appears to only render the last image.
function renderPlayer() {
var imagesList = <%=imageList %>;
var size = imagesList.length;
for(var key in imagesList)
{
var image = imagesList[key][1];
var gridPosition = imagesList[key][0];
var playerCanvas = document.getElementById(gridPosition);
window.context2 = playerCanvas.getContext("2d");
//context2.save();
playerCanvas.setAttribute("width", 97);
var imageObj = new Image();
imageObj.onload = function(){
context2.drawImage(imageObj, 0, 10, 97, 97);
};
imageObj.src = image;
}
}
Does anyone have any suggestions why this may not be working?
Same old problem... all of your image onload methods refer to one and only imageObj that gets overwritten every time through the loop.
for(var key in imagesList)
{
var image = imagesList[key][1];
var gridPosition = imagesList[key][0];
var playerCanvas = document.getElementById(gridPosition);
window.context2 = playerCanvas.getContext("2d");
//context2.save();
playerCanvas.setAttribute("width", 97);
(function(){
var imageObj = new Image();
imageObj.onload = function(){
context2.drawImage(imageObj, 0, 10, 97, 97);
};
imageObj.src = image;
})();
}
by wrapping in a immediately executing function block, each imageObj becomes it's own variable, which each onload function captures.
The first example is failing because you need to wait for the image to be loaded before you can draw it in the canvas. The randomness is caused by the fact that sometimes the image will have loaded by the time you run the context2.drawImage(..) command and sometimes it won't, by placing this code inside the onload handler of the image you should guarantee that the image data is present.
The second example looks better, but you should change onLoad to onload and move the lines
var playerCanvas = document.getElementById(gridPosition);
var context2 = playerCanvas.getContext("2d");
outside the for loop, also note the change to the second line.

Categories