Javascript Image.onload callback to object function - javascript

This is driving me nuts - I have been round and round in circles, and this no joy. I am loading multiple dynamic images, and have a simple Javascript object which I instantiate for each image, and which has a callback to render the image once it has loaded asynchronously.
I have verified that the callback code works just fine on a stand-alone basis (ie. I can call the callback 'manually' after the image has loaded, and the image is rendered correctly), and I have verified that the image itself is successfully loaded (by switching the object's callback for a simple one line logging function), but when I try to tie it all together, the callback apparently never gets invoked.
I'm relatively new to JS and I suspect that I am missing something fundamental about the way functions within objects are defined, but despite a lot of Googling, can't work out what.
Please can someone show me the error of my ways?
function ImageHolder(aX,aY,aW,aH, anImageURL) {
this.posx=aX;
this.posy=aY;
this.posw=aW;
this.posh=aH;
this.url=anImageURL;
this.myImage = new Image();
this.myImage.onload=this.onload;
this.myImage.src=anImageURL;
this.onload=function() {
try {
var d=document.getElementById("d");
var mycanvas=d.getContext('2d');
mycanvas.drawImage(this.myImage, this.posx, this.posy, this.posw, this.posh);
} catch(err) {
console.log('Onload: could not draw image '+this.url);
console.log(err);
}
};
}

You have two problems: first, this.onload is not defined at the point at which you assign it to the image. You can fix this by missing out the stage of storing the onload handler function as a property of this. The second problem is that when the onload handler function is called, this is not set to what you think it is (in fact, it's a reference to the Image object that has just loaded). You need to store a reference to the current ImageHolder object and use it instead of this within the event handler function.
New code:
var that = this;
this.myImage = new Image();
this.myImage.onload=function() {
try {
var d=document.getElementById("d");
var mycanvas=d.getContext('2d');
mycanvas.drawImage(that.myImage, that.posx, that.posy, that.posw, that.posh);
} catch(err) {
console.log('Onload: could not draw image '+that.url);
console.log(err);
}
};
this.myImage.src = anImageURL;

Related

How can I re-load images in p5.js once they've changed since runtime?

I have a python file outputting a .png file to a local directory once a second, and this p5.js Javascript file is being hosted from this directory on a server. My issue is in trying to use the loadImage() function to load this local .png -- if I do so, it'll only refer to the initial value of it, and not its new changed value. Even when using a callback function inside draw(), it is very buggy, and does not change. Here's the file:
let splot;
//...
//irrelevant classes
//...
function draw() {
background(245);
solOnePane.display();
s1plot = loadImage("plot.png", imageLoaded);
}
function imageLoaded()
{
console.log("Debug");
image(s1plot,200,200);
}
The result is a super buggy and unchanging image file that only reflects the image file at the state of loading the site. Upon a refresh, the file is updated. So, how can I write code with loadImage() to load an image at the current moment?
Thanks so much.
If the image is saved at a set internal externally, let's say every second, you could use setInterval() every 1100ms for example to reload the image:
var s1plot;
function setup(){
//...init your things here
//reload the image every 1.1s
setInterval(loadImage, 1100, "plot.png", imageLoaded, imageLoadFailed);
}
function imageLoaded(loadedImage)
{
s1plot = loadedImage;
console.log("image loaded", s1plot);
}
function imageLoadFailed(event){
console.warn("image load failed", event);
}
function draw() {
background(245);
if(s1plot){// try to display the image only when it's available
image(s1plot, 200, 200);
}
}
You could use draw() as well and change the frameRate() to so a new frame is rendered every second or so. Here's a modified version of the above using frameRate():
var s1plot;
function setup(){
//...init your things here
//set frameRate to 1 frame every 1.1s
frameRate(1000 / 1100);
}
function imageLoaded(loadedImage)
{
s1plot = loadedImage;
console.log("image loaded", s1plot);
}
function imageLoadFailed(event){
console.warn("image load failed", event);
}
function draw() {
background(245);
if(s1plot){// try to display the image only when it's available
image(s1plot, 200, 200);
}
// reload the image
loadImage("plot.png", imageLoaded, imageLoadFailed);
}
This may be simpler (sometimes setInterval's scope can get hairy), but it also means you're limited to slow render updates. You may want this if the p5 sketch if simply displaying an image, but not if for example you want to handle reactive user interaction as well.
The code above isn't tested, but hopefully illustrates the idea.
It's worth double checking loadImage() reference when in doubt.
For example, these details are important:
The image may not be immediately available for rendering. If you want to ensure that the image is ready before doing anything with it, place the loadImage() call in preload(). You may also supply a callback function to handle the image when it's ready.
In your case the second option (supplying a callback function) applies, however you've forgotten to add the image argument to the image load callback.
This assumes there are no issues writing plot.png to disk with no errors and the timing is a bit of a hack.
In an ideal world you would have the first system which writes plot.png send a message to p5 when the image has been successuflly been written and is ready to be loaded. (If plot.png is for example generated by a python script using matplotlib or something similar you could use a server sent event or websocket library to which p5 can connect to and listen for the message to load the image)

Loop on loading images, one by one, only after previous has been fully loaded

As the title suggests, I have a list of images that I want loaded. I have a function that given a numerical value from 1 to 1000 (approx.) changes the source of the image object in my html document to be the desired image name. This numerical value is kept in a global variable, which may change at times, and then, by calling that function, the actual image src will change as well.
For example:
if ( myImage == 1)
document.getElementById("mainImage").src = "snow.bmp";
else if ( myImage == 2)
document.getElementById("mainImage").src = "desert.bmp";
else if ( myImage == 3)
document.getElementById("mainImage").src = "forest.bmp";
And so on. I know, this can be done in a better looking way, rather than using if-else every time, but that's not my current issue.
What I noticed is that every image takes some time to load during my main index.html when placed on remote servers (locally it loads fast), so in order to overcome this, I wanted to preload all the images, with a nice advancement bar on the side or some numerical track.
To do so, I created a separate html, that will be some sort of an intro. I want to iterate over all possible numerical values, lets say, ranging 1 to 1000. Each time, updating myImage to be that value. After a value is assigned to myImage, the function makeImage(), which is the if-else from above, is called. Then, the actual source of mainImage is changed accordingly. This completes a single iteration. Only after the image is loaded to the user, do I want to move to the next value in the range [1,1000]. I tried reading online about wait and async and promises, but could not bring to implement this for my case.
So far my code is built of the main method to load:
async function loadAllImages()
{
for(var valueOfCell = 1; valueOfCell <= 1000; valueOfCell ++)
{
myImage = valueOfCell;
makeImage();
let loadedImage = await loadImage();
}
}
And the waiting function is:
function loadImage() {
const img = new Image();
return new Promise((resolve, reject) => {
img.addEventListener("load", () => {
resolve();
});
img.addEventListener("error", () => {
reject();
});
img.src = document.getElementById("mainImage").src;
});
}
One of the problems is, I am using an Image object, which, to be honest, I have no use in. I might as well say that the above code is combined from stuff I found online, I just wasn't sure how to use await.
I would be very glad if someone could assist me in this, especially to understand the usage of await and how to use it for my case.

Accessing a loaded Table in drop()

I am trying to load a simple CSV file in my sketch through the drop() function in p5. I can successfully 'get' the file and call loadTable(), however I would like to do something with the loaded table automatically, and for some reason it seems like I have to let the drop() function completely finish before being able to access the table.
My little test sketch 'gets' a file that is dragged onto the canvas, loads it into a table, and attempts to print out the getRowCount() immediately after loading. This returns 0.... so I also set up a function to run getRowCount() when the mouse is clicked, and this works as expected.
My test CSV file: https://drive.google.com/open?id=1NOluhKiqMxZy10s3dAFLsHLLjoAtV6GT
I only partially understand why this is happening, and I definitely don't know how to get around it. I've been teaching myself Javascript and p5, so I don't know the terms that I need to search to understand what is happening here...
var myTable;
function setup() {
var canvas = createCanvas(400, 400);
canvas.drop(getFile);
}
function draw() {
background(220);
}
function getFile(file) {
myTable = loadTable(file.data);
// Do something with the table *when* I drop it...
console.log("In getFile function: " + myTable.getRowCount());
// Doesn't work either...
extra(myTable);
}
function mouseClicked() {
console.log("On mouse click " + myTable.getRowCount());
}
function extra(table_) {
console.log("In extra function: " + table_.getRowCount());
}
I'd recommend always looking in the reference when you have questions about how P5.js works.
Here is the reference for the loadTable() function. It says:
This method is asynchronous, meaning it may not finish before the next line in your sketch is executed. Calling loadTable() inside preload() guarantees to complete the operation before setup() and draw() are called.
Outside of preload(), you may supply a callback function to handle the object:
Syntax
loadTable(filename, options, [callback], [errorCallback])
loadTable(filename, [callback], [errorCallback])
You would need to provide a callback function that is triggered when the loadTable() function is finished asynchronously loading the table.

Calling function in a callback before defining it in javascript?

I am having some trouble wrapping my head around this. I have a web application that is almost entirely built with javascript. It starts out with a basic template, then starts adding content to it as the user interacts. I am trying to use Greensock as the animation library which has the ability to use a progress slider to show how far you are in the animation, see the second box here: https://greensock.com/timelinemax
The issue is that it uses a callback onUpdate that is supposed to run that function on each frame. Then I can use it to make the slider track with the animation.
var mainTL = new TimelineLite({onUpdate:updateSlider});
function updateSlider() {
sliderTimeline.noUiSlider.set( mainTL.progress());
}
This would work - except that the slider object doesn't exist yet. I don't know why, this is some of the last code to be included in the file, but I get a couple errors in the console log just loading the page `ReferenceError: sliderTimeline is not defined' but then everything works.
To try getting away from those errors, I tried to do it like this:
var mainTL = new TimelineLite({onUpdate:updateSlider});
$( document ).ready(function() {
function updateSlider() {
sliderTimeline.noUiSlider.set( mainTL.progress());
}
});
except now it fails because the updateSlider' function hasn't been defined, and it fails to start at all. I could put them both in a$( document ).ready(function()`, but then they become local functions / variables and the 5 other javascript files I am working with don't have access to them.
Do I have to live with the errors, or is there something I am not thinking of?
You can check whether sliderTimeline exists before trying to call it. For example change function updateSlider() to:
function updateSlider() {
if (typeof sliderTimeline !== 'undefined') {
sliderTimeline.noUiSlider.set( mainTL.progress());
}
}
Or if you know that sliderTimeline is declared, but not assigned yet:
function updateSlider() {
if (sliderTimeline) {
sliderTimeline.noUiSlider.set( mainTL.progress());
}
}
Note that this works because onUpdate is called frequently, so it will eventually be called when sliderTimeline is eventually defined.
Edit:
Additionally, you can assign global variables inside $( document ).ready() as long as you declare them outside of the function.
For example:
var mainTL;
var updateSlider;
$( document ).ready(function() {
updateSlider = function () {
sliderTimeline.noUiSlider.set( mainTL.progress());
};
mainTL = new TimelineLite({onUpdate: updateSlider});
});
If you look at their codepen page http://codepen.io/GreenSock/pen/FnsqC/ they have:
var tl = new TimelineMax({delay:0.5, repeat:3,
repeatDelay:2, onUpdate:updateStats,
onRepeat:updateReps, onComplete:restart});
function updateReps() {
reps++;
repeatCount.innerHTML = reps;
}
function updateStats() {
time.innerHTML = tl.time().toFixed(2);
totalTime.innerHTML = tl.totalTime().toFixed(2);
progress.innerHTML = tl.progress().toFixed(2);
totalProgress.innerHTML = tl.totalProgress().toFixed(2);
}
Meaning that you need to define the callback function of onUpdate.

JavaScript - overwriting .onload prototype of HTMLImageElement(s)

Is it possible to bind an onload event to each image, declaring it once? I tried, but can't manage to get it working... (this error is thrown: Uncaught TypeError: Illegal invocation)
HTMLImageElement.prototype.onload = function()
{
console.log(this, "loaded");
};
P.S: I also tried returning this, but doesn't seem to be the issue here... any suggestions / explanations on why my current code isn't working?
You can't set a handler on the prototype, no.
In fact, I'm not aware of any way to get a proactive notification for image load if you haven't hooked load on the specific image element, since load doesn't bubble.
I only two know two ways to implement a general "some image somewhere has loaded" mechanism:
Use a timer loop, which is obviously unsatisfying on multiple levels. But it does function. The actual query (document.getElementsByTagName("img")) isn't that bad as it returns a reference to the continually updated (live) HTMLCollection of img elements, rather than creating a snapshot like querySelectorAll does. Then you can use Array.prototype methods on it (directly, to avoid creating an intermediary array, if you like).
Use a mutation observer to watch for new img elements being added or the src attribute on existing img elements changing, then hook up a load handler if their complete property isn't true. (You have to be careful with race conditions there; the property can be changed by the browser even while your JavaScript code is running, because your JavaScript code is running on a single UI thread, but the browser is multi-threaded.)
You get that error because onload is an accessor property defined in HTMLElement.prototype.
You are supposed to call the accessor only on HTML elements, but you are calling the setter on HTMLImageElement.prototype, which is not an HTML element.
If you want to define that function, use defineProperty instead.
Object.defineProperty(HTMLImageElement.prototype, 'onload', {
configurable: true,
enumerable: true,
value: function () {
console.log(this, "loaded");
}
});
var img = new Image();
img.onload();
Warning: Messing with builtin prototypes is bad practice.
However, that only defines a function. The function won't be magically called when the image is loaded, even if the function is named onload.
That's because even listeners are internal things. It's not that, when an image is loaded, the browser calls the onload method. Instead, when you set the onload method, that function is internally stored as an event listener, and when the image is loaded the browser runs the load event listeners.
Instead, the proper way would be using Web Components to create a custom element:
var proto = Object.create(HTMLElement.prototype);
proto.createdCallback = function() {
var img = document.createElement('img');
img.src = this.getAttribute('src');
img.addEventListener('load', function() {
console.log('loaded');
});
this.appendChild(img);
};
document.registerElement('my-img', {prototype: proto});
<my-img src="/favicon.ico"></my-img>
There is not much browser support yet, though.
This provides a notification for any image loading, at least in Opera (Presto) and Firefox (haven't tried any other browser). The script tag is placed in the HEAD element so it is executed and the event listener installed before any of the body content is loaded.
document.addEventListener('load', function(e) {
if ((!e.target.tagName) || (e.target.tagName.toLowerCase() != 'img')) return;
// do stuff here
}, true);
Of course, by changing the filtering on tagName it will also serve to respond to the loading of any other element that fires a load event, such as a script tag.
I've written something similar some time ago to check if an image is loaded or not, and if not, show a default image. You can use the same approach.
$(document).ready(function() {
// loop every image in the page
$("img").each(function() {
// naturalWidth is the actual width of the image
// if 0, img is not loaded
// or the loaded img's width is 0. if so, do further check
if (this.naturalWidth === 0) { // not loaded
this.dataset.src = this.src; // keep the original src
this.src = "image404.jpg";
} else {
// loaded
}
});
});

Categories

Resources