How to be Notified When Images Get Loaded in HTML


First published: 16/08/2024

I recently created a web component to render masonry layouts, masonry-gallery.

It does this by calculating the width and height of the images, and ordering them based on those dimensions. But I came across a bunch of problems pretty quickly.

For starters, there’s no guarantee that the custom element is registered immediately, by the time it is registered some of its child images may have loaded while others may not have.

For galleries with dozens of images, the images could take a long time to load. To ensure that the masonry gallery renders the optimal layout, the layout algorithm needs to run every single time an image has loaded.

When does the load event fire?

I did this by attaching a load listener on to the custom element itself. While the load event runs when child images load, it also runs when:

  • The HTML page finishes loading
  • iFrames, and other external resources finish loading

External resources can be loaded via link, embed, script and other HTML elements, not just img. It’s important to add a check to ensure that the layout algorithm doesn’t run when load events from elements other than img are dispatched.

Using event delegation for load events

I mentioned a second ago that I attached a single listener to the custom element that listeners to load events fired from the children. This is known as event delegation (which I’ll talk more about in a future newsletter). This is an alternative for adding event listeners to each individual child.

Here’s an HTML grid that contains several images:

<div class="grid">
	<img src="1.png" />
	<img src="2.png" />
	<img src="3.png" />
	<img src="4.png" />
</div>

You might think to attach an event listener to the parent element, like so:

const grid = document.querySelector('.grid');
grid.addEventListener('load', () => {
	generateLayout();
});

However, this approach doesn’t work. This does work for other events, like click. So what gives?

It turns out that the load event doesn’t bubble. Meaning that after the img element runs its load event handler, the event stops propagating. As a result, the event listener I added to the grid element doesn’t fire, meaning generateLayout doesn’t get invoked.

To solve this problem, you can add an option to addEventListener so that the even fires during the capture phase and not the bubble phase:

const grid = document.querySelector('.grid');
grid.addEventListener(
	'load',
	() => {
		generateLayout();
	},
	{ capture: true }
);


If you enjoyed this article, then I figure you like to be kept in the loop with web development tips. If so, the Component Odyssey newsletter might be right up your alley.