Last active
November 4, 2019 22:02
-
-
Save benrobertsonio/00c5b47eaf5786da0759b63d78dfde9e to your computer and use it in GitHub Desktop.
Lazy Loading Video Based on Connection Speed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is probably the only case I see a valid reason to use
Promise.race
. Hats off