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.
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:
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.
load
eventsI 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.