An image can still be loading or decoding after you set its src. A preview function that immediately measures or draws the image can run before the browser has decoded it. The result can be an empty preview even though the selected file is valid.
HTMLImageElement.decode() provides a promise for that boundary. Await it before drawing. Handle rejection as an input failure, and leave the previous preview visible until a replacement is ready.
Give each selection its own image
This example accepts an image File and an existing canvas. It is intended for still images that the browser can decode. It does not add support for a format the browser cannot read.
let selectionVersion = 0;
async function updatePreview(file, canvas) {
const version = ++selectionVersion;
const url = URL.createObjectURL(file);
const img = new Image();
try {
img.src = url;
await img.decode();
if (version !== selectionVersion) return;
const width = img.naturalWidth;
const height = img.naturalHeight;
if (!width || !height) throw new Error('Missing image dimensions');
const ctx = canvas.getContext('2d');
if (!ctx) throw new Error('2D canvas is unavailable');
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0);
} finally {
URL.revokeObjectURL(url);
}
}
The caller should catch a rejected updatePreview promise and show a useful message. Report success after the preview is ready. The file picker only confirms that an input was selected.
The dimensions here are the image element's natural, density-corrected dimensions. They do not constitute a direct reading of the original file header. For an ordinary local photograph they are useful for sizing a canvas, but the distinction matters when documenting what a measurement means.
Keep a slow selection from replacing a newer one
Imagine selecting file A and then file B while A is still decoding. B may finish first. Without a check, A can arrive later and overwrite the preview of B, leaving a mismatch between the selected filename and the visible image.
The version counter allows only the most recent call to draw. It does not cancel the decoder or release its work immediately. Each call still reaches its finally block and revokes its own object URL. A production interface also needs to associate any error message with the current selection, so an old failure does not appear beside a newer successful preview.
For a small test, select a large photograph and quickly select a second, visibly different one. Confirm that the final preview matches the current filename. Then select a file the browser cannot decode and check that the error path leaves the interface usable. These are proposed test steps, not benchmark results.
Account for the canvas size
The function draws at natural dimensions. That is useful for explaining readiness, but it can allocate a large canvas. A thumbnail interface should calculate a smaller output size before assigning the canvas dimensions. Decoding the source may still require substantial resources, so resizing the destination alone is not a memory budget.
The object URL is revoked after the image has been drawn. If you also assign that URL to a separate preview element or a download control, its lifetime needs to cover those uses. Do not copy this cleanup point into a different flow without checking who still needs the URL.
References: MDN decode() and naturalWidth.
Top comments (0)