vanilla-lazyload icon indicating copy to clipboard operation
vanilla-lazyload copied to clipboard

Is it possible to lazyload an iframe only if the scrolling stops for X seconds?

Open alvarotrigo opened this issue 1 year ago • 1 comments

I have plenty of iframes on a page. About 25. So, to improve the performance of the site while the user is scrolling up/down I'd like to only lazy load the iframe if the user stops scrolling for a few seconds and the element is on the viewport.

Here's a great example of what I'd like to do: https://swiperjs.com/demos

Would this be possible with vanilla-lazyload?

alvarotrigo avatar Oct 26 '23 18:10 alvarotrigo

Hey @alvarotrigo,
thanks fur reaching out.

The answer is yes, but you will need to use an older version: 15.2.0, because the load_delay option was removed from version 16.

In the changelog, you will find everything about releases.

If you need specific documentation for that version, you will need to check out the repo at the tag 15.2.0 and read the readme.md file.

Let me know if it works for you!

verlok avatar Oct 26 '23 21:10 verlok

Hey @alvarotrigo, do you still need help with this issue?
If not, how did you solve?

verlok avatar Mar 29 '24 17:03 verlok

Hey @alvarotrigo, do you still need help with this issue?
If not, how did you solve?

I created my own custom solution. It's a pity we can't use this library to do it, but well, this works so far to fix performance issues:


let scrollTimeout;

function demo() {
    console.log("Scrolling stopped for more than 1.5 seconds.");

    // Example usage:
    const elements = document.querySelectorAll('.lazy[data-src]');

    elements.forEach((element) => {
        if (isElementInViewport(element)) {
            console.log(element);
            element.setAttribute('src', element.getAttribute('data-src'));
            element.removeAttribute('data-src');
            // Perform actions on the visible element here
        }
    });

}

function handleScrollEvent() {
  // Clear the previous timeout if it exists
  clearTimeout(scrollTimeout);

  // Set a new timeout for 1.5 seconds
  scrollTimeout = setTimeout(() => {
    demo();
  }, 300);
}

// Add an event listener for the 'scroll' event on the document
document.addEventListener("scroll", handleScrollEvent);


function isElementInViewport(el) {
  const rect = el.getBoundingClientRect();
  const windowHeight = (window.innerHeight || document.documentElement.clientHeight);
  const windowWidth = (window.innerWidth || document.documentElement.clientWidth);

  // Check if any part of the element is visible in the viewport
  const partiallyVisible = (
    rect.top <= windowHeight &&
    rect.bottom >= 0 &&
    rect.left <= windowWidth &&
    rect.right >= 0
  );

  return partiallyVisible;
}

Just make sure to update the code accordingly. This line should contain your selector.

 const elements = document.querySelectorAll('.lazy[data-src]');

alvarotrigo avatar Mar 29 '24 17:03 alvarotrigo

Hey @alvarotrigo, as I told you in my first reply, there was a delay option which was removed in version 16. So if you just download version 15 of this lazy load library, you have it in the options.

Also I checked your solution and I see it's relying on getBoundingClientRect. This is very bad for web performance because it forces the browser to recalculate style at each scroll event. If you don't believe me, run a performance trace in the browser developer tools or just google.

So if you decide not to use version 15 of this library, at least be sure to rewrite your custom solution using IntersectionObserver.

verlok avatar Mar 30 '24 06:03 verlok

More precisely, 15.2.0 is the version you might want to use.

We could introduce load_delay in this version again, though.

verlok avatar Mar 30 '24 07:03 verlok

Hi @alvarotrigo

I'm writing to you because I happened upon your issue and because something similar happened to me in the past... yes, with a little trick you can do what you ask with vanilla-lazyload!

Let me explain, first you need to load the script at in the header so the lazy load will not work automatically. Second, you need to add a listener to the scroll that fires the lazyload and then shut down the observer again

<html>
  <head>
  <script src="../dist/lazyload.min.js"></script>
  <script>
      (function () {
      let ll = new LazyLoad({});
      
      // update the LazyLoad instance when the window is resized
      window.addEventListener('DOMContentLoaded', () => {
        ll.update();
      });
      
      let scrollTimeout;
      
      // update the LazyLoad instance when the window is scrolled
      window.addEventListener('scroll', () => {
        clearTimeout(scrollTimeout);
        scrollTimeout = setTimeout(() => {
          console.log('window scrolled');
          ll.update();
        }, 1000);
        ll._observer.disconnect()
      });
      })();
  </script>
  </head>
  <body>
      ....
  </body>
</html>

demo: stackblitz.com

That's it, let me know if that has worked!

erikyo avatar Apr 02 '24 21:04 erikyo

Also I checked your solution and I see it's relying on getBoundingClientRect. This is very bad for web performance because it forces the browser to recalculate style at each scroll event. If you don't believe me, run a performance trace in the browser developer tools or just google.

I totally believe you :)

Thanks for the tip! 👍 I'll just update the code to use IntersectionObserver. Not really a fun of using old versions of libraries :)


const observer = new IntersectionObserver((entries, observer) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        // If the element is visible, start a timeout
        entry.target.visibilityTimeout = setTimeout(() => {
          const element = entry.target;
          console.log("Element has been visible for at least 300ms", element);
          if (element.getAttribute('data-src')) {
            element.setAttribute('src', element.getAttribute('data-src'));
            element.removeAttribute('data-src');
            // Perform actions on the visible element here
          }
          observer.unobserve(element); // Stop observing the current target
        }, 300);
      } else {
        // If the element is no longer visible, clear the timeout
        if (entry.target.visibilityTimeout) {
          clearTimeout(entry.target.visibilityTimeout);
        }
      }
    });
  }, {
    root: null, // observing for viewport
    rootMargin: '0px',
    threshold: 0.1 // Callback is executed when 10% of the element is visible
  });

  // Example usage:
  const elements = document.querySelectorAll('.lazy[data-src]');
  elements.forEach((element) => {
    observer.observe(element); // Start observing the element
  });

Thanks @erikyo ! Although I think I'll end up using my custom solution as it gives me more flexibility and doesn't force to to preload the JS on the head.

alvarotrigo avatar Apr 08 '24 04:04 alvarotrigo

Good point.

And good code, this is exactly what this library was doing with the load_delay option.

Actually I'm not sure of where I was saving timer id, I should check, but your option of adding a new property to the DOM element totally works. It's not maybe 100% legit, but it works.

verlok avatar Apr 08 '24 05:04 verlok