Lazy Loading Video
When you lazy load a video, you postpone loading the video until playback is required. The opposite - preloading - stresses the page load speed and data cap of your viewers.
This article lists techniques for on-demand loading, and describes how to apply these techniques to a HTML <video>
, embedded video players (e.g. YouTube) and JavaScript video players.
Techniques
You can combine the following techniques to implement lazy loading in your web app.
<video> API
Let's analyse the following <video>
tag and its attributes.
<video preload="none" src="video.mp4"
autoplay="false" poster="poster.jpg"
muted="false" loop="false"
width="100%" height="100%">
</video>
The src
, preload
, autoplay
and poster
attributes are related to lazy loading.
src
sets the video source. You can also configure this through a<source>
node instead.preload
preloads data. If yoursrc
is configured, setpreload
tonone
to avoid preloading. Ifsrc
(or<source>
) isn't configured, you can usepreload="auto"
because preloading won't start until there's an associated video source.autoplay
starts playing the video automatically, which means the video download starts.poster
sets a placeholder image until video playback starts. This placeholder image must also be downloaded, so you also want to lazy load the poster if possible.
You can find a list of all video attributes at https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video.
You can use the HTMLMediaElement
interface to manipulate the <video>
element with JavaScript, after obtaining a reference to the <video>
element (e.g. video = document.querySelector('video')
). The following properties and methods are related to lazy loading:
video.load()
loads some initial video data.video.play()
starts playback.video.preload = "auto"
indicates that some data must be preloaded.
Intersection Observer API
The Intersection Observer API allows developers to programmatically detect the visibility of a DOM element – and also a video element or iframe.
let options = {
root: document.querySelector('#scrollArea'),
rootMargin: '0px',
threshold: 1.0
}
let observer = new IntersectionObserver(callback, options);
let target = document.querySelector('#listItem');
observer.observe(target);
As the snippet indicates, you can observe the visibility of a targeted DOM element. You can pass along a callback
function to execute logic when the target is visible – for example, you could preload the video.
The alternative? Use scroll
, resize
, or orientationchange
event handlers
Async JavaScript
You can make your JavaScript files load asynchronously by using the async
attribute.
<script src="video-player.js" async></script>
When you mark a script as async
, your JavaScript is no longer parser blocking. When your script is parser blocking, the web browser pauses DOM construction when encountering your script. (In layman's terms: your viewers get a shitty browsing experience because your page pauses loading text, containers, etc. for a bit.)
Lazy Loading Libraries
Why re-invent the wheel? There are a number of JavaScript libraries which combine the mentioned techniques.
- LazyLoad uses the Intersection Observer API and can be used to lazy load videos, images and iframes. The library supports async loading.
- lazyYT is jQuery library to lazy load YouTube iframes.
- Lazy load XT is jQuery library to lazy load videos, (YouTube) iframes and images.
Alternatives
Wouldn't it be awesome if there was a native lazy loading API? Something like <video loading="lazy" src="video.mp4" poster="poster.jpg"></video>
for example? Well, as of April 2020, the loading="lazy"
attribute is actually supported on Chromium and Firefox for <img>
tags. The attribute is also supported for <iframe>
tags on Chromium. There's currently no support for <video>
tags though.
HTML Video
The following snippet uses the <video> API and Intersection Observer API to achieve lazy loading.
Quick heads-up: there's a video below the snippet. This video starts lazy loading when the video is visible.
...
<video autoplay="false" muted loop
playsinline width="100%" height="auto"
data-poster="https://ottball.com/content/images/2020/06/image-3.png"
class="lazy">
<source data-src="https://storage.googleapis.com/ottball-b-1/videos/tos/youcompress.mp4"
type="video/mp4">
</video>
...
<script>
document.addEventListener("DOMContentLoaded", function() {
var videos = [].slice.call(document.querySelectorAll("video.lazy"));
if ("IntersectionObserver" in window) {
var videoObserver = new IntersectionObserver(function(entries, observer) {
entries.forEach(function(video) {
if (video.isIntersecting) {
video.poster = video.dataset.poster;
for (var source in video.target.children) {
var videoSource = video.target.children[source];
if (typeof videoSource.tagName === "string" && videoSource.tagName === "SOURCE") {
videoSource.src = videoSource.dataset.src;
}
}
video.target.load();
video.target.classList.remove("lazy");
videoObserver.unobserve(video.target);
}
});
});
videos.forEach(function(video) {
videoObserver.observe(video);
});
}
});
</script>
...
We adapted this snippet from Google's Developers Blog.
The following snippet could be used to achieve lazy loading through the LazyLoad library.
<video
class="lazy"
controls
width="100%"
data-src="https://storage.googleapis.com/ottball-b-1/videos/tos/youcompress.mp4"
data-poster="https://ottball.com/content/images/2020/06/image-3.png">
<source type="video/mp4"
data-src="https://storage.googleapis.com/ottball-b-1/videos/tos/youcompress.mp4" />
</video>
<script>
window.lazyLoadOptions = {
elements_selector: ".lazy"
};
</script>
<script
async
src="https://cdn.jsdelivr.net/npm/vanilla-lazyload@16.1.0/dist/lazyload.min.js">
</script>
Embedded Players
The following snippet uses the Intersection Observer API to achieve lazy loading for iframes. We'll use YouTube as our sample case.
Another quick heads-up: there's an iframe below the snippet. This iframe starts lazy loading when the iframe is visible.
...
<style>
.iframe-container {
position: relative;
width: 100%;
height: 0;
padding-bottom: 56.25%;
}
.iframe-video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
</style>
...
<div class="iframe-container">
<iframe class="youtube-video iframe-video lazy"
data-src="https://www.youtube.com/embed/vx5-fQ67yDA"
frameborder="0"
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen></iframe>
</div>
...
<script>
document.addEventListener("DOMContentLoaded", function () {
if ("IntersectionObserver" in window) {
var iframes = document.querySelectorAll("iframe.lazy");
var iframeObserver = new IntersectionObserver(function (entries, observer) {
entries.forEach(function (entry) {
if (entry.isIntersecting && entry.target.src.length == 0) {
entry.target.src = entry.target.dataset.src;
iframeObserver.unobserve(entry.target);
}
});
});
iframes.forEach(function (iframe) {
iframeObserver.observe(iframe);
});
} else {
var iframes = document.querySelector('iframe.lazy');
for (var i = 0; i < iframes.length; i++) {
if (lazyVids[i].getAttribute('data-src')) {
lazyVids[i].setAttribute('src', lazyVids[i].getAttribute('data-src'));
}
}
}
})
</script>
...
We adapted the JavaScript from Chris' blog to load the iframe on-demand. This snippet puts the iframe in a container, to make the iframe responsive to a 16:9 aspect ratio. When the iframe is visible to the user, the JavaScript grabs the data-src
from the <iframe>
, configures it as the src
and loads the content of the iframe.
JavaScript Players
With JavaScript video players, the same principles as HTML videos apply: load the video when the video (player) becomes visible. Additionally, you want to asynchronously load their JavaScript libraries.
This codepen.io snippet uses the Intersection Observer API, the JavaScript API of the video player and async JavaScript to achieve lazy loading with the video player below.
Conclusion
Developers can use the Intersection Observer API to lazy load videos in order to optimize the critical rendering path.
There's a trade-off to consider when doing lazy loading videos.
- The advantage? Lazy loading can decrease the page load time and can save the viewer some bandwidth.
- The disadvantage? Lazy loading can increase the video start-up time. Akamai writes that viewers start abandoning a video if it takes longer than 2 seconds to load, and that an extra 5.8% drops off for every additional second of load time.
You should leverage lazy loading when it makes sense for your use-case. If you're building a video streaming website, you probably want to preload some videos. If you have a website/blog where video is secondary, you probably want to implement lazy loading. If you're doing livestreaming, it might not even make sense to preload data, because loaded data will be outdated when you hit play.
Resources
- https://developer.mozilla.org/en-US/docs/Web/Performance/Lazy_loading
- https://developers.google.com/web/fundamentals/performance/lazy-loading-guidance/images-and-video#lazy_loading_video
- https://developers.google.com/web/fundamentals/performance/critical-rendering-path/adding-interactivity-with-javascript#parser_blocking_versus_asynchronous_javascript
- https://web.dev/native-lazy-loading/