Below is a simple, lazy, technique I applied in a collection management tool where its users wanted to browse over 10.000 images without scroll-hijacking or pagination. Sure, only the HTML weight several MB’s at once, but for this particular application used by professionals it is worth the weight. But performance was too heavily affected by downloading all these separate images.
So let’s have a look how I solved this.
What does the code below do?
<figure class="image">
<noscript data-lazy="lazy-load">
<img class="nonlinked_show" decoding="async" src="/uploads/images/1540215129.jpg" alt="tree in field" />
</noscript>
</figure>
You rightly guessed that it won’t display any image to say ~95% of the users. The 5% who have disabled Javascript however, will see it.
The 95% who do have Javascript can enjoy the image simply by moving the img tag out of the noscript tag using a bit of JavaScript:
ELEMENTS_QUERY = "noscript[data-lazy=\"lazy-load\"]"
decode_entities = (encodedString)->
# required for IE and Safari
textArea = document.createElement('textarea')
textArea.innerHTML = encodedString
textArea.value
lazy_load_image_in_noscript_wrapper = (noscript_tag)->
fragment = noscript_tag.innerHTML
parent = noscript_tag.parentElement
parent.innerHTML = decode_entities(fragment)
lazy_load_images = ->
noscript_wrapped_images = document.querySelectorAll(ELEMENTS_QUERY)
noscript_wrapped_images.forEach(lazy_load_image_in_noscript_wrapper)
document.addEventListener "ready", ->
lazy_load_images()
Yeah, I don’t enjoy typing ;’s, so it’s Coffeescript here. It also reads more like pseudocode 🙂 You can convert it here.
But this ain’t lazy loading yet. It brings back the images though. And next up is actually making the images load lazily.
I promised to keep this lazy. So I’m only going to implement lazy loading (which I consider an progressive improvement over traditional loading) for newer browsers that actually support the IntersectionObserver. Currently supported by almost 70% of the browsers (including Firefox, Chrome, Edge), soon close to 80% when a new version of Safari is being released.
Here is the rewritten lazy_load_images()
-method:
lazy_load_images = ->
noscript_wrapped_images = document.querySelectorAll(ELEMENTS_QUERY)
supportsIntersectionObserver = typeof IntersectionObserver == "function"
if supportsIntersectionObserver
intersectionObsOptions =
root_margin: "100px"
intersectionObserver = new IntersectionObserver((entries)->
entries.forEach(process_intersection_observer_entry)
, intersectionObsOptions)
noscript_wrapped_images.forEach((e)->intersectionObserver.observe(e.parentElement))
else
noscript_wrapped_images.forEach(lazy_load_image_in_noscript_wrapper
And there you have it. Lazy lazy loading. Make sure that you prevent reflows though. Without proper styling your page may have to be re-rendered all the way down when a single image is triggered for loading. So make sure there is an relatively positioned container for your images which removes the position of the images from the document flow.
Bonus points: how to make it work for print:
show_all_images = ->
noscript_wrapped_images = document.querySelectorAll(ELEMENTS_QUERY)
noscript_wrapped_images.forEach(lazy_load_image_in_noscript_wrapper)
# handle pritning of images: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeprint
window.addEventListener "beforeprint", ->
show_all_images()
mediaQueryList = window.matchMedia('print');
mediaQueryList.addListener (mql)->
if mql.matches
show_all_images()
I hope you enjoyed this. If so, sign-up for my mailinglist to get the latest in your inbox!
Enjoyed this? Follow me on Mastodon or add the RSS, euh ATOM feed to your feed reader.
Dit artikel van murblog van Maarten Brouwers (murb) is in licentie gegeven volgens een Creative Commons Naamsvermelding 3.0 Nederland licentie .