aFarkas / lazysizes

High performance and SEO friendly lazy loader for images (responsive and normal), iframes and more, that detects any visibility changes triggered through user interaction, CSS or JavaScript without configuration.
MIT License
17.47k stars 1.73k forks source link

Help required building a preloader #583

Closed LeoSeyers closed 5 years ago

LeoSeyers commented 5 years ago

Hello @lazysizers

I'm currently building a portfolio website for a photographer. ( see http://victorpattyn.com/black, under devlopment ). It relies on Fullpage and Flickity for the layout. Both libraries have build-in lazyload options but so far only Lazysizes matches my needs. (Thank you so much for the hard work and dediction, really helps).

It works great and I've implemented a LQUIP pattern in since we target the creative industry folks, we want to deliver high quality images and to mitigate potential speed issues. My ultimate goal is thus to build a preloader animation that would preserve user experiences while the scripts are initializing. I want to make sure the preloader is removed only when near view elements are loaded.

I've tried several approaches but I'm currently stucked because I have inconsistent events. It supposed to work when images are cached as well. I should be able to check the progression as well because I want to animate the preloader accordingly.

Here is how I initialize Lazysizes

<script src="//cdnjs.cloudflare.com/ajax/libs/lazysizes/4.1.4/lazysizes.min.js"></script>
  <script>
    window.lazySizesConfig = window.lazySizesConfig || {};
    lazySizesConfig.loadMode = 3;
    lazySizesConfig.init = false;
  </script>

Rough version of the preloader logic (that assume naively that all 'beforeunveil' events happen before associated 'load' events)

const initLazyCounter = () => {

  document.addEventListener('lazybeforeunveil', e => {
    unveilEvents++
    e.target.addEventListener('load', () => {
      loadEvents++
      loadPercentage()

      if (unveilState && unveilEvents >= loadEvents) {
        console.log('all images unveled and loaded')
        isReady = true // remove preloader
      }
    })
  })

  document.addEventListener('lazybeforeunveil', debounce(function () {
    allImagesAreUnveiled()
  }, 1000))

lazySizes.init()

}

const allImagesAreUnveiled = () => {
  console.log('allImagesAreUnveiled')
  unveilState = true
}

const loadPercentage = () => {
  console.log(loadEvents / unveilEvents)
  // will return this value to animate the preloader accordingly
}

The bottleneck of when events are triggered timeline viewport

aFarkas commented 5 years ago

You can not do it like this. lazysizes loads in view images and near view images with different priorities. This is a good thing if you wait for near view images you are destroying the user experience for user that could already see the image but now have to wait for near view images that they are not able to see.

I suggest to use the blur up plugin in addition and use the following script to remove your loader:

// Either:
// import lazySizes from 'lazysizes';
// or
// const lazySizes = window.lazySizes;

/**
 *
 * @type {Promise<any>}
 *
 * @example
 *
 * lazyimagesLoaded.then(() => {
 *     // all in view images are loaded
 * });
 */
const lazyimagesLoaded = new Promise((resolve) => {
    let isCleared;
    let loading = 0;
    const clearLoading = (e) => {
        if (!isCleared) {
            isCleared = true;
            document.removeEventListener('lazybeforeunveil', onUnveil);
        }

        if(loading < 1) {
            resolve();
        }
    };
    const onUnveil = ({target}) => {
        loading++;
        setTimeout(clearLoading, 10);

        target.addEventListener('lazyloaded', onLoaded);
    };
    const onLoaded = ({target}) => {
        loading--;
        clearLoading();
        target.removeEventListener('lazyloaded', onLoaded);
    };
    const clearCheck = () => {
        if (!lazySizes.init.i) {
            setTimeout(clearCheck, 100);
        } else {
            setTimeout(clearLoading, 200);
        }
    };

    clearCheck();

    document.addEventListener('lazybeforeunveil', onUnveil);
});

You should also think about using more srcset candidates you can let lazysizes calculate the sizes attribute using the parent fit plugin.