Best practices from open source - Use img.decode() in image-heavy applications

May 28, 2024 (5mo ago)

I came across a function img.decode() in Next.js source code. I read the MDN docs related to image.decode function. I reviewed the code that was in the img.decode proximity in image-component.tsx and identified the following best practices from image-component.tsx

  1. How img.decode() Works?
  2. Example Usage
  3. Use Cases
  4. Keep src as your last attribute for an image element
  5. ‘Load’ Event and Object.defineProperty to add properties
  6. Register onLoad event only after the image is loaded otherwise onLoad is not registered

How img.decode() Works

  1. Promise-Based: The method returns a promise that resolves when the image is successfully decoded. If the image cannot be decoded (e.g., due to a corrupted file), the promise will be rejected.
  2. Non-Blocking: It allows you to asynchronously decode the image, which means you can handle other tasks while the image is being processed.

img.decode() can be used to initiate loading of the image prior to attaching it to an element in the DOM (or adding it to the DOM as a new element), so that the image can be rendered immediately upon being added to the DOM. This, in turn, prevents the rendering of the next frame after adding the image to the DOM from causing a delay while the image loads. — MDN Docs

Example Usage

Here is a basic example of how img.decode() can be used:

const img = new Image();
img.src = 'path/to/image.jpg';

img.decode().then(() => {
 document.body.appendChild(img);
 console.log('Image has been decoded and appended to the document.');
}).catch((error) => {
 console.error('Image decoding failed:', error);
});

Use Cases

  • Progressive Loading: In scenarios where you want to load images progressively and display them only when they are ready.
  • Image Galleries: When dealing with image-heavy applications like galleries or portfolios, ensuring that images are fully decoded can enhance performance.

when loading very large images (for example, in an online photo album), you can present a low resolution thumbnail image initially and then replace that image with the full-resolution image by instantiating a new HTMLImageElement, setting its source to the full-resolution image’s URL, then using decode() to get a promise which is resolved once the full-resolution image is ready for use. At that time, you can then replace the low-resolution image with the full-resolution one that’s now available. — MDN docs

  • Lazy Loading: Combined with lazy loading strategies, it can ensure images are ready when they enter the viewport.

Keep src as your last attribute for an image element

I found from the comments shown in the above image that safari browser fetches the src immediately before other attributes such as sizes and srcSet are updated by React and it is mentioned that this leads to multiple unnecessary requests once these attributes (sizes and srcSet) are updated by React. This issue is known to occur only in Safari browser. Keep src as your last attribute for an image element.

‘Load’ Event and Object.defineProperty to add properties

You can create a ‘load’ event using the following syntax:

const event = new Event('load')

We all are familiar with the following code snippet:

const event = new Event('load');
event.target = img;

But what if you want to make “target” immutable? Use Object.defineProperty as shown below:

const event = new Event('load');
Object.defineProperty(event, 'target', { writable: false, value: img });

Register onLoad event only after the image is loaded otherwise onLoad is not registered

One thing I like about Next.js source code comments is that, authors put the links pointing to their research. This link could be pointing to medium, stackoverflow, github discussion or any other blog for future references. You should consider adding some links as comments to justify your approach and to show the research efforts you have made. Helps with a smooth code review.

This comment points to a stackoverflow question related to an onLoad event handler failing to register when the image loads quicker than a script, this question has clear screenshots explaining the issue in depth and provides a answer to use image.complete which is found to be used in image-component.tsx

Conclusion:

For image-heavy applications, it is important to optimise for the image rendering. One such method to optimise the large image loading is to use image.decode(). MDN Docs provides a use case that suggests to show a low resolution image thumbnail and then replace it with high resolution after the image.decode() promise resolves.

In the code around the proximity of image.decode() in Next.js source code, I could identify best practices such keeping src as a last attribute for an image to support safari browser, how an onLoad event handler can go unregistered if the image loads quicker than a script.ts that has the onLoad handler, thanks to a comment pointing to stackoverflow question.

About me:

Website: https://ramunarasinga.com/

Linkedin: https://www.linkedin.com/in/ramu-narasinga-189361128/

Github: https://github.com/Ramu-Narasinga

Email: ramu.narasinga@gmail.com

References:

  1. https://github.com/vercel/next.js/blob/canary/packages/next/src/client/image-component.tsx#L11
  2. https://github.com/search?q=img.decode%28%29&type=code
  3. https://react.dev/reference/react/use
  4. https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/decode
  5. https://stackoverflow.com/questions/39777833/image-onload-event-in-isomorphic-universal-react-register-event-after-image-is
  6. https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event