How do you make images load lazily only when they are in the viewport?
Asked Answered
C

4

42

I am seeing a lot of sites these days, mainly tutorial sites that have a lot of images and they only load images further down the page once they come into the view port?

How would I go about doing this? As you scroll down the page the images who were below the viewport fade in

Concertante answered 23/2, 2010 at 21:34 Comment(0)
B
14

http://www.appelsiini.net/projects/lazyload
https://github.com/tuupola/jquery_lazyload

Demo:
http://www.appelsiini.net/projects/lazyload/enabled.html

Battles answered 23/2, 2010 at 21:54 Comment(3)
The LazyLoad plugin is no longer available according to its website. See my answer below.Lunkhead
Looks like the project is alive (again). It's on GitHub, too: github.com/tuupola/jquery_lazyload.Lithology
Aren't link-only answers a bit ... Unreliable because the links can get invalid over time, completely removing the answer's meaning?Grillage
P
63

<img loading="lazy" does it without any Javascript

We are now getting more and more support for this standardized no-JavaScript method, which is very exciting!

You can see this at work in the code snippet below.

To see that the loading is actually lazy, open Chrome DevTools in the Network tab.

Then, as you scroll down the snippet, you will see that the images only load when you see them.

I've also added an optional JavaScript button to show that you can change lazy back to the default eager from JavaScript, and images will then start to load immediately.

document.getElementById('load-now').addEventListener('click', function(){
  for (const img of document.getElementsByTagName('img')) {
    img.loading = 'eager';
  }
});
.separator {
    height: 1000px;
    width: 100px;
    border: 5px solid red;
}
img {
    height: 340px;
    border: 5px solid black;
}
  #load-now {
  border: 5px solid black;
  }
<div id="load-now">Click me to load all images now!</div>
<div><img loading="lazy" height="340" src="https://upload.wikimedia.org/wikipedia/commons/5/56/Donald_Trump_official_portrait.jpg"></div>
<div class="separator"></div>
<div><img loading="lazy" height="340" src="https://upload.wikimedia.org/wikipedia/commons/8/8d/President_Barack_Obama.jpg"></div>
<div class="separator"></div>
<div><img loading="lazy" height="340" src="https://upload.wikimedia.org/wikipedia/commons/d/d4/George-W-Bush.jpeg"></div>
<div class="separator"></div>
<div><img loading="lazy" height="340" src="https://upload.wikimedia.org/wikipedia/commons/d/d3/Bill_Clinton.jpg"></div>
<div class="separator"></div>
<div><img loading="lazy" height="340" src="https://upload.wikimedia.org/wikipedia/commons/9/90/George_H._W._Bush%2C_President_of_the_United_States%2C_1989_official_portrait_%28cropped%29.jpg"></div>
<div class="separator"></div>
<div><img loading="lazy" height="340" src="https://upload.wikimedia.org/wikipedia/commons/1/16/Official_Portrait_of_President_Reagan_1981.jpg"></div>

One really cool thing about this method is that it is fully SEO friendly, since the src= attribute contains the image source as usual, see also: Lazy image loading with semantic markup

Tested in Chromium Chromium 81 and Firefox 77.0.1, both worked and loaded lazily.

IntersectionObserver minimal runnable example

This is a JavaScript method that would work before img loading="lazy" was implemented.

This is essentially the technique used at: https://appelsiini.net/projects/lazyload/ which was mentioned at: https://mcmap.net/q/339891/-how-do-you-make-images-load-lazily-only-when-they-are-in-the-viewport

Web APIs have evolved so much now that it is not hard to code it from scratch!

var observer = new IntersectionObserver(
    (entries, observer) => {
        entries.forEach(entry => {
            if (entry.intersectionRatio > 0.0) {
                img = entry.target;
                if (!img.hasAttribute('src')) {
                    alert('will load the image!!!');
                    img.setAttribute('src', img.dataset.src);
                }
            }
        });
    },
    {}
)
for (let img of document.getElementsByTagName('img')) {
    observer.observe(img);
}
.separator {
    height: 1000px;
    width: 100px;
    border: 5px solid red;
}
img {
    height: 340px;
    border: 5px solid black;
}
<div><img data-src="https://upload.wikimedia.org/wikipedia/commons/5/56/Donald_Trump_official_portrait.jpg"></div>
<div class="separator"></div>
<div><img data-src="https://upload.wikimedia.org/wikipedia/commons/8/8d/President_Barack_Obama.jpg"></div>
<div class="separator"></div>
<div><img data-src="https://upload.wikimedia.org/wikipedia/commons/d/d4/George-W-Bush.jpeg"></div>
<div class="separator"></div>
<div><img data-src="https://upload.wikimedia.org/wikipedia/commons/d/d3/Bill_Clinton.jpg"></div>
<div class="separator"></div>
<div><img data-src="https://upload.wikimedia.org/wikipedia/commons/9/90/George_H._W._Bush%2C_President_of_the_United_States%2C_1989_official_portrait_%28cropped%29.jpg"></div>
<div class="separator"></div>
<div><img data-src="https://upload.wikimedia.org/wikipedia/commons/1/16/Official_Portrait_of_President_Reagan_1981.jpg"></div>

Full page demo: https://cirosantilli.com/web-cheat/js-image-load-viewport.html

GitHub upstream: https://github.com/cirosantilli/cirosantilli.github.io/blob/1f637bf4791b115777300f48f427f0a6bb409fc1/web-cheat/js-image-load-viewport.html

This technique is just a combination of:

Tested in Chromium 76.

Change load order to nearest first

This is the last use case missing after loading="lazy" for me: a method that downloads eagerly, but changes the download order to download first on viewport, then below, and then above: Change loading order of images already on page

Maybe we can do something with querySelectorAll() to solve jQuery find next/prev elements of a certain class but not necessarily siblings and then remove loading=lazy from images in the JavaScript! This would both degrade gracefully, and be SEO friendly.

The last issue is how to get the first visible element though:

I haven't seen a very good solution for this yet.

Lazy load video

Not sure why, but neither Chromium 81 nor Firefox 77.0.1 can lazy load video, now sure why did they do it just for img?

Chromium 81 did implement it for iframe however, which is what YouTube embeds use, while Firefox 77.0.1 didn't: lazy load iframe (delay src http call) with jquery

Pileous answered 7/8, 2019 at 8:0 Comment(0)
P
25

A simple pure JavaScript solution:

<script>
  document.addEventListener("DOMContentLoaded", function () {
    let images = document.querySelectorAll("img[data-src]");
    function loadImagesLazily(e) {
      for (let i = 0; i < images.length; i++) {
        let rect = images[i].getBoundingClientRect();
        if (images[i].hasAttribute("data-src")
          && rect.bottom > 0 && rect.top < window.innerHeight
          && rect.right > 0 && rect.left < window.innerWidth) {
          images[i].setAttribute("src", images[i].getAttribute("data-src"));
          images[i].removeAttribute("data-src");
        }
      }
    };

    window.addEventListener('scroll', loadImagesLazily);
    window.addEventListener('resize', loadImagesLazily);
    loadImagesLazily();
  });
</script>
Poltroonery answered 21/4, 2017 at 17:25 Comment(4)
What part requires that the browser be fairly new? getBoundingClientRect seems fully supported.Roman
You probably want to bind to the 'resize' event as wellSaleswoman
It's working great! by the way, you had to replace all the occurences of <img src="/myimage.png" /> in the HTML (it's not noted in the answer) by: <img src="" realsrc="/myimage.png" /> If you want to avoid any warning by your IDE, use data-src instead of realsrc (replace it also in the javascript)Carcajou
Great, simple and fast !!Swanhilda
B
14

http://www.appelsiini.net/projects/lazyload
https://github.com/tuupola/jquery_lazyload

Demo:
http://www.appelsiini.net/projects/lazyload/enabled.html

Battles answered 23/2, 2010 at 21:54 Comment(3)
The LazyLoad plugin is no longer available according to its website. See my answer below.Lunkhead
Looks like the project is alive (again). It's on GitHub, too: github.com/tuupola/jquery_lazyload.Lithology
Aren't link-only answers a bit ... Unreliable because the links can get invalid over time, completely removing the answer's meaning?Grillage
P
13

Replace your images with placeholders (e.g. just change the "src" attribute to something else so the image won't load, but the url will still be accessible), and then bind the window scroll event to a function which will find all images at the current scroll position, and swap the image src into a real img tag.

Here's the code. It's untested, but this should be the basic idea:

<img src="" realsrc="/myimage.png" />

$(document).ready(function(){

  $(window).scroll(function(){
    $('img[realsrc]').each(function(i){
      var t = $(this);
      if(t.position().top > ($(window).scrollTop()+$(window).height()){
        t.attr('src', t.attr('realsrc')); // trigger the image load
        t.removeAttr('realsrc'); // so we only process this image once
      }
    });
  })

});
Print answered 23/2, 2010 at 21:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.