4

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
  • 4,809
  • 13
  • 53
  • 70
  • When I attempt to add the tag "picture" or "picture-element" for this question, it gets converted to "image". But just to clarify, I am specifically looking at lazy loading an image defined with the picture tag and srcset. – jerome Apr 22 '15 at 18:49
  • The browser lazy loads the picture element for you, if you want to replicate the entire process the browser uses and use JavaScript (presumably to be able to add in some sort of visual transition), then you don't need to (and probably shouldn't) use the picture element as your placeholder. – Adam Jenkins Apr 22 '15 at 18:56
  • So a picture element that is "below the fold" or otherwise out of the viewport (off-screen in a carousel, etc.) does not load until it enters the viewport? – jerome Apr 22 '15 at 19:22
  • I used terminology incorrectly. I meant lazy load as in loads only the necessary image (out of the list) depending on the viewport/device. The picture element specification does not specify how the browser should load images outside of the viewport, so they'll be loaded on first render. – Adam Jenkins Apr 22 '15 at 19:29
  • Got it. Then to clarify, for anybody else who comes across this question, I am looking to defer the http requests for the actual images until the picture element is in the viewport. Thanks! – jerome Apr 22 '15 at 20:14
  • But you are going to have to duplicate the exact logic that the browser uses (because you need to determine which img to show via JS), so again, why do you need the picture element at all? – Adam Jenkins Apr 22 '15 at 22:05
  • Solution just posted, demo to follow: https://stackoverflow.com/a/54092875/5858395 – Sean Doherty Jan 08 '19 at 13:33

3 Answers3

9

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

alexander farkas
  • 13,754
  • 4
  • 40
  • 41
  • 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? – jerome Apr 23 '15 at 14:20
  • Yes, exactly and yes you can use requireJS to load lazysizes. lazysizes itself is using the unnamed umd module pattern. – alexander farkas Apr 23 '15 at 15:27
  • 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 Apr 23 '15 at 15:57
  • One more question @alexander. How is lazysizes with elements that are added to the DOM asynchronously? – jerome Apr 24 '15 at 20:04
  • It handles those automatically. No need to do anything. – alexander farkas Apr 24 '15 at 22:40
  • Thank you, once again @alexander. – jerome Apr 25 '15 at 21:06
  • 6
    It looks like you are the author of this library. Please disclose your affiliation in your answer as per https://stackoverflow.com/help/promotion – Andy Mar 02 '18 at 11:42
3

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;
      });
    }
  });
Sean Doherty
  • 2,273
  • 1
  • 13
  • 20
  • This only lazy loads the `source` elements, but doesn't lazy load the `img` elements. – hostingutilities.com Jan 05 '20 at 07:09
  • @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. – Sean Doherty Jan 13 '20 at 10:09
  • If you scroll down to the bottom images that are lazy loaded, you'll see that you have `image` 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. – hostingutilities.com Jan 14 '20 at 00:12
  • @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. – Sean Doherty Jan 14 '20 at 10:03
  • Here is an example of what I'm talking about. https://codepen.io/mrme44/pen/MWYXPxL – hostingutilities.com Jan 14 '20 at 16:15
  • 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. – Sean Doherty Jan 14 '20 at 21:45
  • 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. https://codepen.io/mrme44/pen/gObjRZj – hostingutilities.com Jan 14 '20 at 22:35
  • 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. – Sean Doherty Jan 15 '20 at 09:26
  • 2
    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. – JD_dev Dec 28 '20 at 06:36
  • 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 tag with a srcset? – nydame Sep 25 '21 at 22:55
0

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);


   })
 }
Nadia
  • 82
  • 8
  • Please dont remove the tag - the tag needs an to be valid html and to fallback correctly on older browsers. – Mike Mar 31 '22 at 09:05