Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save rahulraghavankklm/ded13dcd6cdaeb84ff4dab8deee83bd5 to your computer and use it in GitHub Desktop.

Select an option

Save rahulraghavankklm/ded13dcd6cdaeb84ff4dab8deee83bd5 to your computer and use it in GitHub Desktop.

Revisions

  1. @benrobertsonio benrobertsonio revised this gist Feb 8, 2019. No changes.
  2. @benrobertsonio benrobertsonio created this gist Feb 8, 2019.
    111 changes: 111 additions & 0 deletions lazy-video-loader.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,111 @@
    class LazyVideoLoader {
    constructor() {
    this.videos = [].slice.call(document.querySelectorAll('.hero__bgvideo'));

    // Abort when:
    // - The browser does not support Promises.
    // - There no videos.
    // - If the user prefers reduced motion.
    // - Device is mobile.
    if (
    typeof Promise === 'undefined' ||
    !this.videos ||
    window.matchMedia('(prefers-reduced-motion)').matches ||
    window.innerWidth < 992
    ) {
    return;
    }

    this.videos.forEach(this.loadVideo.bind(this));
    }

    loadVideo(video) {
    this.setSource(video);

    video.load();

    this.checkLoadTime(video);
    }

    /**
    * Find the children of the video that are <source> tags.
    * Set the src attribute for each <source> based on the
    * data-src attribute.
    *
    * @param {object} video The video element.
    * @returns {void}
    */
    setSource(video) {
    const children = [].slice.call(video.children);
    children.forEach((child) => {
    if (
    child.tagName === 'SOURCE' &&
    typeof child.dataset.src !== 'undefined'
    ) {
    child.setAttribute('src', child.dataset.src);
    }
    });
    }

    /**
    * Checks if the video will be able to play through before
    * a predetermined time has passed.
    * @param {object} video The video element.
    * @returns {void}
    */
    checkLoadTime(video) {
    // Create a promise that resolves when the
    // video.canplaythrough event triggers.
    const videoLoad = new Promise((resolve) => {
    video.addEventListener('canplaythrough', () => {
    resolve('can play');
    });
    });

    // Create a promise that resolves after a
    // predetermined time (2sec)
    const videoTimeout = new Promise((resolve) => {
    setTimeout(() => {
    resolve('The video timed out.');
    }, 2000);
    });

    // Race the promises to see which one resolves first.
    Promise.race([videoLoad, videoTimeout]).then((data) => {
    if (data === 'can play') {
    video.play();
    setTimeout(() => {
    video.classList.add('video-loaded');
    }, 500);
    }
    else {
    this.cancelLoad(video);
    }
    });
    }

    /**
    * Cancel the video loading by removing all
    * <source> tags and then triggering video.load().
    *
    * @param {object} video The video element.
    * @returns {void}
    */
    cancelLoad(video) {
    const children = [].slice.call(video.children);
    children.forEach((child) => {
    if (
    child.tagName === 'SOURCE' &&
    typeof child.dataset.src !== 'undefined'
    ) {
    child.parentNode.removeChild(child);
    }
    });

    // reload the video without <source> tags so it
    // stops downloading.
    video.load();
    }
    }

    new LazyVideoLoader();