Lazyloading picture elements

12,139

Solution 1

Use lazysizes, it is a high performance lazyloader for normal and responsive images (including the picture element).

Solution 2

I've created a pen with a solution here:

https://stackoverflow.com/a/54092875/5858395

codepen

Tested in Chrome & Firefox, Safari and IE11 (fallbacks for the latter)

<!-- Load images above the fold normally -->
<picture>
  <source srcset="img/city-m.jpg" media="(max-width: 960px)">
  <source srcset="img/city-l.jpg" media="(min-width: 961px)">
  <img class="fade-in" src="img/city-l.jpg" alt="city"/>
</picture>

<picture>
  <source srcset="img/forest-m.jpg" media="(max-width: 960px)">
  <source srcset="img/forest-l.jpg" media="(min-width: 961px)">
  <img class="fade-in" src="img/forest-l.jpg" alt="forest"/>
</picture>

<!-- Lazy load images below the fold -->
<picture class="lazy">
  <source data-srcset="img/river-m.jpg" media="(max-width: 960px)">
  <source data-srcset="img/river-l.jpg" media="(min-width: 961px)">
  <img data-srcset="img/river-l.jpg" alt="river"/>
</picture>

<picture class="lazy">
  <source data-srcset="img/desert-m.jpg" media="(max-width: 960px)">
  <source data-srcset="img/desert-l.jpg" media="(min-width: 961px)">
  <img data-srcset="img/desert-l.jpg" alt="desert"/>
</picture>

And the JS:

document.addEventListener("DOMContentLoaded", function(event) {
   var lazyImages =[].slice.call(
    document.querySelectorAll(".lazy > source")
   )

   if ("IntersectionObserver" in window) {
      let lazyImageObserver = 
       new IntersectionObserver(function(entries, observer) {
          entries.forEach(function(entry) {
           if (entry.isIntersecting) {      
              let lazyImage = entry.target;
              lazyImage.srcset = lazyImage.dataset.srcset;
              lazyImage.nextElementSibling.srcset = lazyImage.dataset.srcset;
              lazyImage.nextElementSibling.classList.add('fade-in');
              lazyImage.parentElement.classList.remove("lazy");
             lazyImageObserver.unobserve(lazyImage);
            }
         });
        });

      lazyImages.forEach(function(lazyImage) {
       lazyImageObserver.observe(lazyImage);
      });
   } else {
     // Not supported, load all images immediately
    lazyImages.forEach(function(image){
        image.nextElementSibling.src = image.nextElementSibling.dataset.srcset;
      });
    }
  });

Solution 3

You don't really need a plugin, just put a class on your picture tag, remove the "img" tag inside it, and place the src inside srcset:

 <picture class="lazy">
   <source srcset='/images/image.jpg'/>
 </picture>

Then in javascript after your dom has loaded:

Get all ".lazy" nodes:

const lazyImages = document.querySelectorAll('.lazy');

Call a function like this:

 function lazyLoadImages() {
   lazyImages.forEach((val,i) => {

     let src = val.querySelector('source').getAttribute('srcset');
     let image = new Image();
     image.src = src;
     val.append(image);


   })
 }
Share:
12,139
jerome
Author by

jerome

Updated on June 04, 2022

Comments

  • jerome
    jerome about 2 years

    I'm looking for recommendations on how best to lazyload picture elements. I may like to use a small jQuery helper function to determine whether the picture is "on screen". But the lazy fetching of the srcset, I'm not sure right now how to do that. So, any ideas?

    Here's an example of the picture elements I am working with. Thanks!

    <picture alt="Random Celebrities" data-src="http://www.example.com/r/c_1,h_478,w_478/2015/03/19/random-celebrities-08-560x560.jpg">
        <!--[if IE 9]><video style="display: none;"><![endif]-->
        <source class='picture-source-1260' srcset='http://www.example.com/r/c_1,h_239,w_239/2015/03/19/random-celebrities-08-560x560.jpg, http://www.example.com/r/c_1,h_478,w_478/2015/03/19/random-celebrities-08-560x560.jpg 2x' media='(min-width: 1260px)'>
        <source class='picture-source-960' srcset='http://www.example.com/r/c_1,h_180,w_180/2015/03/19/random-celebrities-08-560x560.jpg, http://www.example.com/r/c_1,h_360,w_360/2015/03/19/random-celebrities-08-560x560.jpg 2x' media='(min-width: 960px)'>
        <source class='picture-source-760' srcset='http://www.example.com/r/c_1,h_150,w_150/2015/03/19/random-celebrities-08-560x560.jpg, http://www.example.com/r/c_1,h_300,w_300/2015/03/19/random-celebrities-08-560x560.jpg 2x' media='(min-width: 760px)'>
        <source class='picture-source-450' srcset='http://www.example.com/r/c_1,h_210,w_210/2015/03/19/random-celebrities-08-560x560.jpg, http://www.example.com/r/c_1,h_420,w_420/2015/03/19/random-celebrities-08-560x560.jpg 2x' media='(min-width: 450px)'>
        <source class='picture-source-320' srcset='http://www.example.com/r/c_1,h_160,w_160/2015/03/19/random-celebrities-08-560x560.jpg, http://www.example.com/r/c_1,h_320,w_320/2015/03/19/random-celebrities-08-560x560.jpg 2x'>
        <!--[if IE 9]></video><![endif]-->
        <noscript>
            <img class="picture-img-noscript" src="http://www.example.com/r/c_1,h_160,w_160/2015/03/19/random-celebrities-08-560x560.jpg" alt="Random Celebrities" />
        </noscript>
        <img class="picture-img" srcset="http://www.example.com/r/c_1,h_160,w_160/2015/03/19/random-celebrities-08-560x560.jpg" alt="Random Celebrities" />
    </picture>
    
  • jerome
    jerome about 9 years
    Thanks for your response @alexander. So would I just change all instances of srcset to data-srcset in all the source tags and add the class "lazyload" to the img tag? And another quick question: can lazysizes be loaded as a module with RequireJS?
  • alexander farkas
    alexander farkas about 9 years
    Yes, exactly and yes you can use requireJS to load lazysizes. lazysizes itself is using the unnamed umd module pattern.
  • jerome
    jerome about 9 years
    I appreciate your quick responses, @alexander. Appreciate that lazysizes uses UMD. Marking this as the best answer. (But I may ask, if I have any more questions!)
  • jerome
    jerome about 9 years
    One more question @alexander. How is lazysizes with elements that are added to the DOM asynchronously?
  • alexander farkas
    alexander farkas about 9 years
    It handles those automatically. No need to do anything.
  • jerome
    jerome about 9 years
    Thank you, once again @alexander.
  • Andy
    Andy over 6 years
    It looks like you are the author of this library. Please disclose your affiliation in your answer as per stackoverflow.com/help/promotion
  • hostingutilities.com
    hostingutilities.com over 4 years
    This only lazy loads the source elements, but doesn't lazy load the img elements.
  • Sean Doherty
    Sean Doherty over 4 years
    @wp-overwatch.com, can you expand please? I just looked at the codpen with the network tab open, and the img get added to the som just before they enter the viewport - as intended and as described.
  • hostingutilities.com
    hostingutilities.com over 4 years
    If you scroll down to the bottom images that are lazy loaded, you'll see that you have <img data-srcset="https://i.ibb.co/D5QyVPk/desert-l.jpg" alt="image" srcset="https://i.ibb.co/D5QyVPk/desert-l.jpg" class="fade-in"> in the DOM which is missing a valid src attribute. The images are still lazy loaded because the source tags are ok, but if you remove all of the source tags so that only the img tags remain, then you'll notice that none of the images load.
  • Sean Doherty
    Sean Doherty over 4 years
    @wp-overwatch.com, sorry, I don't really understand. What would be the solution? No errors in console and all 4 images are loading at the time I expect them to.
  • hostingutilities.com
    hostingutilities.com over 4 years
    Here is an example of what I'm talking about. codepen.io/mrme44/pen/MWYXPxL
  • Sean Doherty
    Sean Doherty over 4 years
    How is that useful in any way? In what scenario would there ever be no sources to choose from? The javascript selects a source based on window size, and maps this src to the img file.
  • hostingutilities.com
    hostingutilities.com over 4 years
    I'm just showing that the img tags are being ignored. Here is a more realistic example. In this codepen the desert image should be showing, but it won't ever be shown. codepen.io/mrme44/pen/gObjRZj
  • Sean Doherty
    Sean Doherty over 4 years
    I'm sorry but one of us is maybe high. There is no desert image referenced in your pen. Just a single river image above the fold that doesn't need and wouldn't use the lazy class.
  • JD_dev
    JD_dev over 3 years
    After an exhaustive search for a solution I can say that the code above is still the only correct answer I've found. None of the popular lazy loading scripts/plug-ins work with the picture tag reliably or at all based on my testing as of December 2020. @wp-overwatch.com is correct that the code ignores an img tag and only addresses the source tags. However the posted code can be easily expanded upon to include lazy loading of the img tag inside the picture tag. Until native browser lazyloading is ubiquitous this solution will still be relevant for complete lazy loading with picture tags.
  • nydame
    nydame almost 3 years
    It wouldn't hurt to check if lazyImage.nextElementSibling.tagname === "IMG", in which case you could give it a "src" attribute instead of "srcset". But maybe modern browsers don't mind an <img> tag with a srcset?
  • Mike
    Mike about 2 years
    Please dont remove the <img> tag - the <picture> tag needs an <img> to be valid html and to fallback correctly on older browsers.