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

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

Related

JavaScript: lifetime of an image declared inside a function with a load listener attached

I need to create a temporary image, wait for it to load extract dataURL from it and remove it from memory, here is my current code:
const get = id => document.getElementById(id);
const handler = evt => {
const icon = new Image();
icon.addEventListener('load', evt => {
// image -> canvas -> dataURL
console.log('boom');
evt.target.removeEventListener('load', handler);
});
icon.src = src;
};
get('btn').addEventListener('click', handler);
const src='https://source.unsplash.com/random/200x200';
fiddle: https://jsfiddle.net/tfoller/Lg6we5cf/12/
It looks like the image lifetime is longer than the handler function because of the listener attached, so I remove the listener after processing, is that the correct way to make sure the image will be picked up by GC?
EDIT following Teemu comments:
const get = id => document.getElementById(id);
const handler = evt => {
const icon = new Image();
icon.addEventListener('load', function loaded(evt) {
// image -> canvas -> dataURL
console.log('boom');
evt.target.removeEventListener('load', loaded);
});
icon.src = src;
};
get('btn').addEventListener('click', handler);
const src='https://source.unsplash.com/random/200x200';

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/

Why am I not getting an onload handler?

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.

Create an Image object from scratch? (Javascript)

I want to create an image object from scratch. Here is the idea:
var image = new Image();
var image2 = new Object();
/* heres where the magic is
\\\\\\\\\\\\\\\\\\\\\\\\\\\
\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
if image = image2
alert('sucess');
The goal is that I need to edit the Image object to accept video files. I suppose the solution could also be prototypical if it changed the structure of the Image object appropriately.
function videotr(){
media = document.createElement('video');
media.preload = true;
//media.controls = true;
media.className = 'c1';
media.id = 'it';
document.getElementById('crop').appendChild(media);
//media.loop = true;
//media.autoplay = true;
return media;
}
this works

Categories