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

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

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

When do DOM elements become live and their events are hooked and (can) fire

For instance an HTMLImageElement has events it can trigger on load success or failure.
These events seem to trigger asynchronously even if the image is not added to a document, or is part of a fragment. They only seem to be suppressed if the image is added to a <template> before the event can fire:
// onerror will fire
const image = document.createElement('img');
image.src = '0'
image.onerror = () => log("detached");
// onerror will fire
const fragment = new DocumentFragment();
const fragmentImage = document.createElement('img');
fragmentImage.src = '0';
fragmentImage.onerror = () => log("fragment");
fragment.appendChild(fragmentImage);
// onerror will *not* fire
const template = document.createElement('template');
const templateImage = document.createElement('img');
template.content.appendChild(templateImage);
templateImage.src = '0';
templateImage.onerror = () => log("template first");
// onerror will fire
const templateImage2 = document.createElement('img');
templateImage2.src = '0';
templateImage2.onerror = () => log("template last");
template.content.appendChild(templateImage2);
I tried looking at the WhatWG spec, but didn't find anything which seemed relevant, or at least not clearly. While the spec precisely describes steps to perform when updating loading or calling decode(), it doesn't seem to really explain the loaded process and how the various events are invoked when src (or srcset I guess) is interacted with.

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/

What happens to event listener allocated to 'reference-free' DOM element when that element is garbage collected?

Similar question
Above link is a similar question what Im about to ask. But I couldn't find any official reference about that answer.
public uploadImage() {
const input: HTMLInputElement = document.createElement('input');
input.type = 'file';
input.accept = 'image/png';
const onChange = () => {
const file = input.files[0];
const reader = new FileReader();
const onError = () => {
reader.abort();
alert('image loading failed');
};
const onLoadEnd = (evt) => {
if (evt.target.readyState === 2) {
this.readImageByBase64(evt.target.result as string);
} else {
onError();
}
};
reader.addEventListener('loadend', onLoadEnd);
reader.addEventListener('error', onError);
reader.readAsDataURL(file);
};
input.addEventListener('change', onChange);
input.click();
}
According to answer to above question, since the input variable and reader variable is function scoped, will event listener be removed from the element and will onLoadeEnd and onError be garbage collected? If yes, are there any background information or reference about it?(more focused about removing event listeners then garbage collection)

Load image from a Buffer/Blob

I am trying to take an image from the clipboard within electron, and put it on a canvas.
In the electron main process, I have this function which is triggered by a menu item. If I write img.toBitmap().toString() to the console it outputs the image data, so I know that there is an image there.
export function pasteImage() {
let img = clipboard.readImage()
console.log('send paste message')
mainWindow.webContents.send('paste-image', img.toBitmap())
}
Next I have this method which takes the buffer and converts it to a blob. It then should load it into the image and draw the image to the canvas.
public pasteImage(image: Buffer, x: number = 0, y: number = 0) {
let blob = new Blob([image], { type: 'image/bmp' })
let url = URL.createObjectURL(blob)
let img = new Image
img.addEventListener('load', () => {
console.log('loaded image')
this.ctx.drawImage(img, x, y)
URL.revokeObjectURL(url)
})
img.src = url
}
ipcRenderer.addListener('paste-image', (e: EventEmitter, img: Buffer) => {
let canvas = document.createElement('canvas')
let layer = new layer(canvas)
layer.pasteImage(img)
})
The issue I am having is that the load event never gets triggered, however the method pasteImage(image, x, y) is executing.

Categories