Smart Lazy-Loading

I was recently building a media carousel for a site and implemented lazy loading for the images. This was great initially. I avoided loading about 20 MB of images on page load (there were multiple carousels, each with many large images), and the images would load as they slid into view.

The Problem

The problem arose when the requirements changed (🫠) and I had to show the images at their natural aspect ratio. Some where portrait and others were landscape. There was a caption element that was positioned absolutely based on the image dimensions, and it now appeared broken for a split second when the carousel indexed. This was happening because the image dimensions were no longer predictable. The <img> tags had an initial width of zero, which would collapse the captions. As the carousel indexed, the collapsed caption would slide into view, then the image would load and pop everything into place.

A video demonstrating collapsed captions when the image carousel indexes.

At first I tried fixing this with CSS. Surely there’s an easy way to tell the browser, “just make the <img> whatever width/height it will be after the image loads”. There is not, unless I’m missing something obvious. I could probably have used JS to set a fixed width/height based on the aspect ratio (width and height attributes of <img>), but I’m not sure that would’ve worked either because flex was involved.

The Solution

It occurred to me that the problem went away once the images loaded, so perhaps the simplest solution was to make the images load sooner. Removing lazy loading entirely was not a good idea (see previous comment about insane amount of images), but as it turns out, it’s pretty easy to lazy load images and then force the browser to load them whenever you want. All you have to do is remove the loading attribute when you want them to load.

On this particular site, the carousels in question were in different tabs of a media section on the page. So, in this case, the solution looked like this:

  • Lazy load all images with loading="lazy".
  • When one of the image carousel tabs is selected (event listener for custom event), it triggers a bit of JS that removes the loading attribute from all the images in that carousel, forcing them to load before they’re seen by the user.

You get the best of both worlds because the initial page load is still as fast as before, but the images are preloaded once the user indicates they want to see them.

JavaScript
// NOTE: this site was already using jQuery and Bootstrap 3.4, these are not my choices...

// Listen for when a tab is activated. If it's a photo gallery, preload the images.
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
  const tabID = e.target.hash
  const photoGallery = document.querySelector(`${tabID} .stallion-gallery--photos:not(.stallion-gallery--dialog)`)

  photoGallery?.StallionPhotoGallery?.loadGalleryImages()
})

// Then in the StallionPhotoGallery class...

// Force lazy-loaded gallery images to load.
loadGalleryImages () {
  this.galleryImages.forEach(image => image.removeAttribute('loading'))
}