Why am I not getting an onload handler? - javascript

I have several images I am trying to load. Each one has is connected to a single handler intended to check if all the images are displaying, and if so, display them on a canvas.
The handler, presently with a console.log() at the top, is:
POCKETWATCH.check_draw_clock = function()
{
console.log("check_draw_clock");
POCKETWATCH.images_loaded_count += 1;
if (POCKETWATCH.images_loaded_count ==
POCKETWATCH.total_images_available * 2)
{
POCKETWATCH.draw_clock();
}
}
I start to load the images:
console.log('Starting images.');
POCKETWATCH.clock_face = new Image();
POCKETWATCH.clock_face.onload = POCKETWATCH.check_draw_clock;
POCKETWATCH.clock_face.src = 'img/transparent-clock.png';
POCKETWATCH.hour_hand = new Image();
POCKETWATCH.hour_hand.onload = POCKETWATCH.check_draw_clock;
POCKETWATCH.hour_hand.src = 'img/hour-hand.png';
POCKETWATCH.minute_hand = new Image();
POCKETWATCH.minute_hand.onload = POCKETWATCH.check_draw_clock;
POCKETWATCH.minute_hand.src = 'img/minute-hand.png';
POCKETWATCH.second_hand = new Image();
POCKETWATCH.second_hand.onload = POCKETWATCH.check_draw_clock;
POCKETWATCH.second_hand.src = 'img/second-hand.png';
POCKETWATCH.dial_hand = new Image();
POCKETWATCH.dial_hand.onload = POCKETWATCH.check_draw_clock;
POCKETWATCH.dial_hand.src = 'img/dial-hand.png';
console.log('Images all started.');
The console after this has no errors, just "Starting images." and "Images all started.", with no "check_draw_clock" and no errors. The images are copied from another project of mine and as far as I can tell there are appropriate filesystem permissions etc. and the images are all there.
How am I getting statements that the images are being loaded with no reference to the callback function and no reported errors?
--UPDATE--
In response to the first answer, I commented out my existing code and added what mutated to:
['img/transparent-clock.png','img/hour-hand.png','img/second-hand.png','img/dial-hand.png'].forEach(function(src)
{
var img = new Image();
// img.onload = POCKETWATCH.check_draw_clock;
img.onerror = function(){ console.log("error"); };
img.src = src;
if (img.complete) { POCKETWATCH.check_draw_clock(); }
console.log(POCKETWATCH.check_draw_clock);
POCKETWATCH.check_draw_clock();
});
This was not intended as a solution, but for diagnostic purposes. The last real line of code got an error logged that POCKETWATCH.check_draw_clock "is not a function," and then when I logged POCKETWATCH.check_draw_clock, it logged "undefined".
I checked through my code to see if I redefined POCKETWATCH; in the entire code associated with the app, the only time POCKETWATCH is defined is above the quoted code, where it is set to {}. check_draw_clock() is also defined only once.
FWIW.

Check to see if the image was cached with complete
['img/transparent-clock.png','img/hour-hand.png','img/second-hand.png','img/dial-hand.png'].forEach(function(src){
var img = new Image();
img.onload = POCKETWATCH.check_draw_clock;
img.onerror = function(){ console.log("error"); };
img.src = src;
if (img.complete) { POCKETWATCH.check_draw_clock(); }
});

Went through to console.log check_draw_clock() all the way, and found that the definition of POCKETWATCH was after what I quoted, not before. Now the logging makes sense.

Related

how do I replace an image with broken link to a default image?

I am not normally one to write JS, but I need this for a little project for my work that displays 4 images on some tv's that management can update. I created a little site that displays the images and need it to refresh the image with new slides every so often as they are updated. This all works fine, but if someone names the image incorrectly it will be a broken link. How can I add a check to see if the image exists and if it doesn't return a default image.
//sleep function
async function sleep(seconds) {
return new Promise((resolve) => setTimeout(resolve, seconds * 1000));
}
async function setImage() {
//get divs by class name
const imageDiv1 = document.querySelector('.image1');
const imageDiv2 = document.querySelector('.image2');
const imageDiv3 = document.querySelector('.image3');
const imageDiv4 = document.querySelector('.image4');
//define const images
const image0 = new Image();
const image1 = new Image();
const image2 = new Image();
const image3 = new Image();
const image4 = new Image();
//define paths
const defaultImage = 'default.png';
const safteyImage = "..\\Safety\\Safety_current.png";
const qualityImage = "..\\Quality\\Quality_current.png";
const omImage = "..\\O&M\\O&M_current.png";
const announcementsImage = "..\\Announcements\\Announcements_current.png";
//set images to paths
image0.src = defaultImage;
image1.src = safteyImage;
image2.src = qualityImage;
image3.src = omImage;
image4.src = announcementsImage;
//add images to canvas
imageDiv1.appendChild(image1);
imageDiv2.appendChild(image2);
imageDiv3.appendChild(image3);
imageDiv4.appendChild(image4);
//infinite loop of updating image.
while(true){
await sleep(5); //only 5 seconds right now for testing.
image1.src = safteyImage + "?" + new Date().getTime();
image2.src = qualityImage + "?" + new Date().getTime();
image3.src = omImage + "?" + new Date().getTime();
image4.src = announcementsImage + "?" + new Date().getTime();
imageDiv1.replaceChild(image1, image1);
imageDiv2.replaceChild(image2, image2);
imageDiv3.replaceChild(image3, image3);
imageDiv4.replaceChild(image4, image4);
}
}
//call setImage on load
window.onload = (event) => {
setImage();
}
I am looking for a way to switch it to a default image within the while loop so it is always checking if it exists or not.
document.getElementsByClassName('.image1').onerror = function() {
document.getElementsByClassName('.image1').src = "default.png";
}
doesnt seem to work for me. I also found a function that checks the status code, but since this is looking into a local file I dont this this approach works, which it didnt.
//check if an image exists, (non working)
function imageExists(image_url){
var request = new XMLHttpRequest();
request.open("GET", image_url, true);
request.send();
request.onload = function() {
imageStatus = request.status;
if(request.status == 200) {
console.log('it exists');
}else{
console.log('nope');
}
}
}
You can listen for the "error" event on the document itself. This obviates the need to attach event listeners on each individual image.
document.addEventListener('error', e => {
e.target.src = 'default.png';
}, true);
Here is the best solution
Use the img tag for the default image:
const image0 = Image();
image0.src = defaultImage;
Then for every image use the object tag, inserting a copy of the default image inside (so that it gets loaded of the object resource fails to load):
const image1 = document.createElement("object");
image1.appendChild(image0.cloneNode());
To set the source of the images use data attribute:
image1.data = safteyImage;
This way if the resource represented by the object tag can't be loaded, the default image gets loaded instead.
by LL

JavaScript all images loading before await

I have a simple JavaScript file that gets multiple image files from a local device and displays a thumb of the images. So far so good.
Once the thumbs are displayed I have a button that calls the addImages() function. I need to display a slide show of the images, one at a time, for 3 seconds. However, what is happening is all the images are showing at the same time, then the sleep function is called, and then remove the image. Here is my code:
function addImages() {
var preview = document.querySelector('#display');
async function readAndPreview(file) {
var reader = new FileReader();
reader.addEventListener("load", async function () {
var image = new Image();
image.height = 400;
image.width = 400;
image.title = file.name;
image.border = 5;
image.src = this.result;
preview.appendChild(image);
sleep(3000).then(()=> preview.removeChild(image))
}, false);
reader.readAsDataURL(file);
}
if(files) {
[].forEach.call(files, readAndPreview);
}
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
It looks like the code iterates through to appendChild and then starts again until all images are added and then calls the sleep functions and then removeChild. Why???
Any help will be appreciated. Thanks.
I have changed a lot of the implementation as proposed by you.
Major changes:-
Having one instance of file reader
Having single image for which we keep on changing the src url after designated time. (Came to this conclusion after I saw that you're trying to append and remove the same image element after certain duration). So yes I am assuming that the focus will always be one image at a time and so the below code.
let files // you must have an array initialized I think
let reader;
const preview = document.querySelector('#display');
let image = new Image();
image.height = 400;
image.width = 400;
image.title = file.name;
image.border = 5;
preview.appendChild(image);
function initReader(){
reader = new FileReader();
reader.addEventListener("load", function () {
image.src = this.result;
}, false);
}
function addImages() {
initReader();
async function readAndPreview() {
for (let index = 0;index<files.length;index++){
reader.readAsDataURL(files[index]);
await sleep(3000);
}
}
readAndPreview();
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
I haven't run this so there might be scope of mistakes. But this approach should work for your use-case.
Also there is scope of improvement in this approach for smooth transition from one image to another. Usually one can preload all images via blobs URL.createObjectURL and store them in an array over which we loop and update the image src as we did above. Will be much faster since the data is already at client side.

Load image without "new Image()"

I'm trying to load an image in JavaScript:
loadImage(src, img) {
return new Promise((resolve, reject) => {
img.onload = resolve;
img.onerror = reject;
img.src = src;
});
}
The img has been created before, but doesn't exist in the DOM yet. It should be appended once it has loaded.
My problem: As far as I understand, it's not actually going to load the image, because the element is not part of the DOM yet.
I tried to use new Image(), because this will load the image source before being inserted into the DOM:
loadImage(src, img) {
return new Promise((resolve, reject) => {
var tmp = new Image();
tmp.onload = resolve;
tmp.onerror = reject;
tmp.src = src;
img.src = src;
});
}
However, this did the loading twice and felt buggy.
I need to keep (and use) the img element, as other event handlers and attributes are attached to it.
What can I do to load the image and resolve the promise once it's done?
Not sure what is wrong with your second approach and why you think it is buggy. I can imagine it loading twice if image is not in cache yet: if you trigger loading two images at the same time, the browser can't know the URL's caching parameters, and should load it for both elements separately. But if you load them in sequence, the second one should pull from cache, and not make a separate request, except if your server is being weird and serving headers that prohibit caching.
function loadImage(src, img) {
return new Promise((resolve, reject) => {
var tmp = new Image();
tmp.onload = () => {
img.src = src;
resolve();
};
tmp.onerror = reject;
tmp.src = src;
});
}
loadImage("https://via.placeholder.com/350x150", document.getElementById('foo'));
<image id="foo">
If I understand you correctly, you want to render the image once it's done loading? Maybe just create the image in the "loadImage" function and then in the "onload" do the appropriate appending. Here's an example what I was thinking:
const loadImage = (src, parentEl) => {
const img = document.createElement('img')
img.src = src
img.onload = () => {
parentEl.appendChild(img)
}
}
const parentEl = document.getElementById('parent')
loadImage('https://fakeimg.pl/640x360', parentEl)
https://jsfiddle.net/2Lp8hyxf/
I think your assumption is wrong. The image gets loaded without it needed to be attached to the DOM.
I modified your function slightly so it resolves with the image tag instead of the load event and it seems to be working fine:
const loadImage = (src, img) => {
return new Promise((resolve, reject)=> {
img.src = src
img.onload = () => {
resolve(img);
}
img.onerror = reject;
})
}
I'm testing it like this:
const img = document.createElement('img');
loadImage('http://deelay.me/1500/https://fakeimg.pl/640x360', img)
.then((img) => {
const parent = document.getElementById('parent');
document.getElementById('parent').append(img);
const text = document.createElement('p');
text.innerText = "The promise resolved now";
parent.append(text);
})
The deelay.me adds an artificial delay to the loading of the image, so you can see that the image is actually loaded and, using the promise, attached to the DOM only after it has been loaded.
Hope this helps.
Fiddle:
https://jsfiddle.net/f4hratje/5/

Is there a successful method to destroy Buffer instances and deallocate the memory used in NodeJS Canvas?

Tried numerous ways to force the GC to cleanup the memory without success.
On top of that:
buf.remove(); // does not exist
delete buf; // does not deallocate the memory
buf = null; // removing references - no result
The problem occurs in this routine:
function loadImage(url, finish){
var Image = Canvas.Image;
request.get({url:url, encoding:null}, responseImage);
function responseImage(err,res,body) {
if(err){
return finish();
}
var image = new Image();
image.onerror = function(e) {
finish();
};
image.onload = function(){
finish(image);
};
image.src = new Buffer(body, 'binary');
}
}
loadImage("http://...", function(image){
if(image){
canvasContext.drawImage(image,0,0,100,100 );
}
});
I got 2 gig of memory after loop created 150 image objects. even assign null to all of it after render process finish still gave me same result of memory leak. so I dig deep to image.cc and found that the solution is simple. Just re assign source of image to null, image object will clean itself then I got my memory back :) (it might trigger onerror event because image load with nothing)
var img = new Image;
img.onload = function() {
//do anything you want ...
//clean it by change src to null
img.src = null;
};
img.src = data;

Drawing an image in javascript won't work when vars are global

Hey I want to draw multiple images, one a canvas background, two a player that can move on top of that canvas. Here is my question: When I put the var canvas and canvas properties globally it doesn't work. But when I put it back in the img.onload function it works!!! Why is this and what options do I have?
var canvas = document.getElementById("Canvas");
canvas.width = 500;
canvas.height = 500;
var g = canvas.getContext("2d");
var img = new Image();
img.src = "Images/Grass Texture.png";
var player1 = new Image();
player1.src = "Images/Players/Mignolet.png";
var player2 = new Image();
player2.src = "Images/Players/De Gea.png";
var football = new Image();
football.src = "Images/Football/Football.png";
img.onload = function()
{
/* The rest of the code draws onto the graphics context */
g.drawImage(img, 0, 0);
g.drawImage(img,1200, 1200, 1200, 1200);
};
It's important to understand that image loading is asynchronous. That means while your image is loading the code will continue despite.
If the browser is not able to load and decode the images in time there won't be any images to draw.
Even when you use the onload handler (which is necessary) you need to think about the asynchronous nature in the rest of the code - perhaps the code continues to other parts which will fail due to dependency on the images.
You can collect image loading like this:
...
/// if you have many images an array could be considered
var img = new Image();
var player1 = new Image();
var player2 = new Image();
var football = new Image();
/// number of images to load
var count = 4;
/// now share a load handler
img.onload = loadHandler;
player1.onload = loadHandler;
player2.onload = loadHandler;
football.onload = loadHandler;
/// set sources to start loading
img.src = "Images/Grass Texture.png";
player1.src = "Images/Players/Mignolet.png";
player2.src = "Images/Players/De Gea.png";
football.src = "Images/Football/Football.png";
function loadHandler() {
/// decrement the counter
count--;
/// when 0 all images has loaded
if (count === 0) startGame();
}
When all images has loaded the script will continue to startGame() which is also where you need to continue your game from:
function startGame() {
...
g.drawImage(img, 0, 0);
g.drawImage(img,1200, 1200, 1200, 1200);
/// Important! continue your code from here
nextStep();
}
So after invoking image loading by setting the sources your script does not call any other steps until startGame() is called.
Now, the loading can fail for various reasons. If you don't handle errors your game might just stop and you won't know why. During development you should of course always check the console for errors (see console, network etc. if any errors occur) but for a user this won't help much.
You should always add an error handler mechanism to the images as well. You can do this by using the onerror and onabort handlers - after setting the onload handler:
var hasErrors = false;
img.onerror = errorHandler;
player1.onerror = errorHandler;
player2.onerror = errorHandler;
football.onerror = errorHandler;
img.onabort = errorHandler; /// or use a separate abort handler
player1.onabort = errorHandler;
player2.onabort = errorHandler;
football.onabort = errorHandler;
In the error handler you can notify the user, or use a fallback, or anything that can possible solve the situation:
function errorHandler(err) {
/// set error flag
hasErrors = true;
/// notify user, post an error message etc. something..
console.log('Could not load image:', this.src, err);
/// call loadHandler() from here to decrement counter and
/// handle situation when all images has reported in for duty (or not)
loadHandler();
}
And then modify the loadHandler to be error aware (just an alert here, but replace with something more useful):
function loadHandler() {
/// decrement the counter
count--;
/// when 0 all images has loaded
if (count === 0) {
if (hasErrors) {
alert('Sorry mate! Error during image loading');
return;
}
startGame();
}
}
That being said, you can also do this by using an image loader that can handle all the setup and events (loading, errors, progress etc.) for you. These are made, incl. my own if I may, YAIL loader (and there are others), for the purpose of saving developers from a lot of problems and headache - and they are free. Something to consider..
Hope this helps!

Categories