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.
Related
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
I want to display images from filesystem could be jpeg, png, nef, raw, svg etc. in browser. Some of these types like nef, raw are not natively supported by browser and I want to convert them to jpeg (and also to compress it in size) and display it. Below is my (not working) code. I have googled extensively and could not find a way to perform the conversion. The below code ends up triggering onerror callback in Image. Im a relative newbie to JS and would appreciate help/apointers on what needs to happen and the whys if you can. Thanks _/_.
private static async toImg(file: File): Promise<HTMLImageElement> {
let img = new Image()
let output = new Promise<HTMLImageElement>((resolve, reject) => {
img.onload = () => {
resolve(img)
}
img.onerror = (err) => {
console.log(err)
}
let reader = new FileReader()
reader.onloadend = (e) => {
let data = reader.result as string
let blob = new Blob([data], {type: 'image/jpeg'})
img.src = URL.createObjectURL(blob)
}
reader.readAsDataURL(file)
})
return output
}
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/
I'm making a form to take users data in an application web, in which I can add a avatar through an input file. This one filters files with image extensions, but my concern is if I can make a file with a script and give it a .jpg extension for example.
The image I can analyze
const reader = new FileReader();
reader.onload = (event: any) => {
console.log(event);
this.avatarUrl = event.target.result;
console.log(this.avatarUrl);
};
reader.readAsDataURL(this.avatarSelected);
but I can't find anything
to differentiate real images and hidden scripts.
Thanks for the answers.
If I undestand: you want to detect if a file is image or not.
Especially in a case of script.js file that was renamed to script.jpg, you want to detect it as NOT image.
The approach is:
create an image element.
load it
reject on error / resolve on success
add timeout: if it doesn't load after X seconds, change the url to fake url in order to stop loading.
function testImage(url) {
return new Promise((resolve, reject) => {
let timer;
const image = new Image();
image.onerror = image.onabort = () => {
clearTimeout(timer);
reject("error");
};
image.onload = function() {
clearTimeout(timer);
resolve("success");
};
timer = setTimeout(function() {
img.src = "fakeUrl"; // stop loading
reject("timeout");
}, 3000);
img.src = url;
});
}
I've got the proof-of-concept I need for loading in multiple images with FileReader.readAsDataURL().
However, my clients' workflow is such that they load in hundreds of images at a time which crashes the browser.
Is there any way to load in these images in as actual thumbnails (16k vs 16Mb)?
First, don't use a FileReader, at all.
Instead, to display any data from a Blob, use the URL.createObjectURL method.
FileReader will load the binary data thrice in memory (one when reading the Blob for conversion, one as Base64 String and one when passed as
the src of your HTMLElement.)
In case of a file stored on the user-disk, a blobURL will load the data only once in memory, from the HTMLElement. A blobURL is indeed a direct pointer to the Blob's data.
So this will already free a lot of memory for you.
inp.onchange = e => {
for(let file of inp.files) {
let url = URL.createObjectURL(file);
let img = new Image(200);
img.src = url;
document.body.appendChild(img);
}
};
<input type="file" webkitdirectory accepts="image/*" id="inp">
Now, if it is not enough, to generate thumbnails, you could draw all these images on canvases and reduce their size if needed.
But keep in mind that to be able to do so, you'd have to first load the original image's data anyway, and that you cannot be sure how will browser cleanup this used memory. So might do more harm than anything by creating these thumbnail versions.
Anyway, here is a basic example of how it could get implemented:
inp.onchange = e => {
Promise.all([...inp.files].map(toThumbnail))
.then(imgs => document.body.append.apply(document.body, imgs.filter(v => v)))
.catch(console.error);
};
function toThumbnail(file) {
return loadImage(file)
.then(drawToCanvas)
.catch(_ => {});
}
function loadImage(file) {
const url = URL.createObjectURL(file);
const img = new Image();
return new Promise((res, rej) => {
img.onload = e => res(img);
img.onerror = rej;
img.src = url;
});
};
function drawToCanvas(img) {
const w = Math.min(img.naturalWidth, 200);
const r = w / img.naturalWidth;
const h = img.naturalHeight * r;
const canvas = Object.assign(
document.createElement('canvas'), {
width: w,
height: h
}
);
canvas.getContext('2d').drawImage(img, 0, 0, w, h);
return canvas;
}
<input type="file" webkitdirectory accepts="image/*" id="inp">