56

I have been searching (unsuccessfully) for a reliable method to lazy load images while using the HTML5 spec for <picture>. Most solutions/plugins out there currently rely on using data- attributes. I could be wrong, but it doesn't seem this method will work in conjunction w/ <picture>.

I'm really just looking to be pointed in the right direction. If anyone has a solution that they're currently using, I'd love to see. Thanks!

Here is standard markup per the HTML5 spec:

<picture width="500" height="500">
    <source media="(min-width: 45em)" src="large.jpg">
    <source media="(min-width: 18em)" src="med.jpg">
    <source src="small.jpg">
    <img src="small.jpg" alt="">
</picture>
6
  • 2
    Can you show your markup, please? By lazy-loading, you mean "load when in viewport"? What methods have you found, can you please post one and show how it might be applied to <pictures> (and why that would not work)? Commented Jun 3, 2014 at 21:49
  • Yes, sorry I made the assumption. By lazy-loading of images, I do mean when they are in the viewport. Most common solution for images I've seen is Lazy Load plugin for jQuery. Commented Jun 3, 2014 at 21:54
  • 2
    Hm, that plugin is no magic. You could easily adapt it to work with <source> elements in a picture, instead of using the src attribute of an img. Commented Jun 3, 2014 at 22:04
  • 1
    @MrRay I don't know how browsers parse (or will parse) <picture> tag, but most likely appropriate image is loaded during html parsing (like they do currently for <img> tag), so the only way to stop it is to use data-src attribute instead of src in <source> tag. But even in this case there are some issues, so in LazyLoadXT we started to use <br> tag instead of <source>: ressio.github.io/lazy-load-xt/demo/picture.htm (I don't remember exactly what was the issue with <source> tag, but that way didn't work in some browsers, while <br> did) Commented Jun 5, 2014 at 17:49
  • Thanks @DenisRyabov. Yes, that's exactly the issue I'm trying to resolve. I've seen this plugin - it's the only one to address the issue. Now I understand why it uses the <br> tag! Commented Jun 6, 2014 at 18:29

4 Answers 4

98

It's February 2020 now and I'm pleased to report that Google Chrome, Microsoft Edge (the Chromium-based Edge), and Mozilla Firefox all support the new loading="lazy" attribute. The only modern browser hold-out is Apple's Safari (both iOS Safari and macOS Safari) but they've recently finished adding it to Safari's codebase, so I expect it will be released sometime this year.

It's February 2024 now and I'm pleased to report that all web-browsers today have supported <img loading="lazy" /> since January 2022.


The loading="lazy" attribute is only for the <img /> element (and not <picture>) but remember that the <picture> element does not represent the actual replaced-content, the <img /> element does (i.e. the image that users see is always rendered by the <img /> element, the <picture> element just means that the browser can change the <img src="" /> attribute. From the HTML5 spec as of February 2020 (emphasis mine):

The picture element is somewhat different from the similar-looking video and audio elements. While all of them contain source elements, the source element's src attribute has no meaning when the element is nested within a picture element, and the resource selection algorithm is different. Also, the picture element itself does not display anything; it merely provides a context for its contained img element that enables it to choose from multiple URLs.

So doing this should just work:

<picture>
    <source media="(min-width: 45em)" srcset="large.jpg" />
    <source media="(min-width: 18em)" srcset="med.jpg" />
    <source src="small.jpg" />
    <img src="small.jpg" alt="Photo of a turboencabulator" loading="lazy" />
</picture>

Note that the <picture> element does not have any width or height attribute of its own; instead the width and height attributes should be applied to the child <source> and <img> elements:

4.8.17 Dimension attributes

The width and height attributes on img, iframe, embed, object, video, source when the parent is a picture element [...] may be specified to give the dimensions of the visual content of the element (the width and height respectively, relative to the nominal direction of the output medium), in CSS pixels.

[...]

The two attributes must be omitted if the resource in question does not have both an intrinsic width and an intrinsic height.

So if you want all <source> images to be rendered as 500px by 500px then apply the size to the <img> element only (and don't forget the alt="" text for vision-impaired users, it's even a legal requirement in many cases):

<picture>
    <source media="(min-width: 45em)" srcset="large.jpg" />
    <source media="(min-width: 18em)" srcset="med.jpg" />
    <source src="small.jpg" />
    <img src="small.jpg" alt="Photo of a turboencabulator" loading="lazy" width="500" height="500" />
</picture>
Sign up to request clarification or add additional context in comments.

6 Comments

@KyleMit thanks, I was juggling with multiple issues, I meant to have linked to stackoverflow.com/questions/2321907/…
Does this mean that attributes width and height also could be in the img?
this is a great attribute but unfortunately it has bugs on some latest versions of chrome, it sadly does not work on chrome v.124 win.10 (( Tested By Several Diffrent Clients And Devices And Networkds And etc.) i'd prefer using javascript to replace src attribute whenever element gets near to be visible
@MahdiarMransouri Do you have a link to a write-up of this bug you’re alleging exists in Chrome 124?
Nope, removing loading='lazy' fixed the problem for both production team and stakeholders so we just did that :))
|
7

Working example of lazy loading images using the picture element and intersection observer tested in Chrome and Firefox. Safari doesn't support intersection observer so the images are loaded immediately, and IE11 doesn't support the element so we fallback to the default img

The media queries in the media attr are arbitrary and can be set to suit.

The width threshold set is 960px - try a reload above and below this width to see either the medium(-m) or large(-l) variation of the image being downloaded when the image is scrolled into the viewport.

Codepen

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

One last thought is that if you change the screen width back and forth, the image files are repeatedly downloaded again. If I could tie the above method in to a cache check then this would be golden...

Comments

6

For anyone still interested... After revisiting this issue, I came across a fairly new script called, Lazysizes. It's actually quite versatile, but more importantly it allows me to do lazy loading of images while utilizing the HTML5 markup as described in the OP.

Much thanks to the creator of this script, @aFarkas.

Comments

5

On web.dev Google advices that picture tags can be loaded lazy.
https://web.dev/browser-level-image-lazy-loading/

Images that are defined using the element can also be lazy-loaded:

<picture>
  <source media="(min-width: 800px)" srcset="large.jpg 1x, larger.jpg 2x">
  <img src="photo.jpg" loading="lazy">
</picture>

Although a browser will decide which image to load from any of the elements, the loading attribute only needs to be included to the fallback element.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.