Published: August 16, 2019, Last updated: July 2, 2026
As with image elements, you may also want to lazy-load video. Videos are commonly loaded with the <video> element, though for videos hosted on other services such as YouTube they may use <iframe>s (in which case check out the article in lazy-loading iframes).
How to lazy-load <video> depends on the use case, as there are a couple of different solutions.
Using the loading attribute
Browsers have started to support the loading attribute on media elements (<video> and <audio>). Where supported this means both the media and any poster image are automatically lazy-loaded just by adding the loading="lazy" attribute:
<video controls loading="lazy" preload="none" poster="one-does-not-simply-placeholder.jpg">
<source src="one-does-not-simply.webm" type="video/webm">
<source src="one-does-not-simply.mp4" type="video/mp4">
</video>
Using loading="lazy" will prevent the poster image from loading at all, until in or near the viewport, and then will use the preload attribute to decide how much of the video to load. This also means autoplay videos won't autoplay until in or near the viewport. The loading="lazy" attribute works with poster, preload, and autoplay attributes and does not replace the need for them.
Browsers that don't support loading attribute on media elements will ignore this attribute, which will result in the poster image loading immediately, and the video loading based on the preload attribute, which we will cover next.
Use the preload attribute to lazy-load video that doesn't autoplay
Avoiding autoplaying videos is usually best practice as it leaves the control with the user. In these cases specifying the preload attribute on the <video> element is the best way to avoid loading the whole video until needed:
<video controls loading="lazy" preload="none" poster="one-does-not-simply-placeholder.jpg">
<source src="one-does-not-simply.webm" type="video/webm">
<source src="one-does-not-simply.mp4" type="video/mp4">
</video>
The previous example uses a preload attribute with a value of none to prevent browsers from preloading any video data, even when in the viewport. The poster attribute gives the <video> element a placeholder that will occupy the space while the video loads.
In most browsers preload defaults to metadata and a portion of the video is preloaded using the Content-Range header. This can result in more data being downloaded than desired—browsers cannot know what bytes the metadata is stored at, and it may not be stored at the beginning of the file. Therefore, the best chance of avoiding loading the video is with using preload="none".
This can be further enhanced to preload the metadata when the user hovers the video with an onmouseenter attribute (or with the equivalent mouseenter event handler):
<video controls
loading="lazy"
preload="none"
poster="one-does-not-simply-placeholder.jpg"
onmouseenter="event.target.setAttribute('preload','metadata')">
<source src="one-does-not-simply.webm" type="video/webm">
<source src="one-does-not-simply.mp4" type="video/mp4">
</video>
This not only reduces the delay when the user goes to play the video, but also shows the duration of the video as soon as they.
Videos can qualify as LCP candidates. A poster image will be quicker to load than the video so where this is an LCP candidate, you should use a poster image, but also preload it with a fetchpriority attribute value of "high", since at the time of writing you cannot specify the fetchpriority for poster attributes:
<link rel="preload" href="one-does-not-simply-placeholder.jpg" as="image" fetchpriority="high">
<video controls preload="none"
poster="one-does-not-simply-placeholder.jpg"
onmouseenter="event.target.setAttribute('preload','metadata')">
<source src="one-does-not-simply.webm" type="video/webm">
<source src="one-does-not-simply.mp4" type="video/mp4">
</video>
Videos that are LCP elements shouldn't use loading="lazy"—in the same way that you shouldn't lazy load LCP images videos.
For video acting as an animated GIF replacement
Autoplaying videos are commonly used for GIF-style quick animations. While animated GIFs enjoy wide use, they're subpar to video equivalents in a number of ways, particularly in file size. Animated GIFs can stretch into the range of several megabytes of data. Videos of similar visual quality tend to be far smaller.
Using the <video> element as a replacement for animated GIF is not as straightforward as the <img> element. Animated GIFs have three characteristics:
- They play automatically when loaded.
- They loop continuously (though that's not always the case).
- They don't have an audio track.
Achieving this with the <video> element looks something like this:
<video autoplay muted loop playsinline>
<source src="one-does-not-simply.webm" type="video/webm">
<source src="one-does-not-simply.mp4" type="video/mp4">
</video>
The loading="lazy" attribute avoids loading the video until near the viewport. The autoplay, muted, and loop attributes are self-explanatory. playsinline is necessary for autoplaying to occur in iOS.
Now you have a serviceable video-as-GIF replacement that works across platforms. But how to go about lazy loading it?
For browsers that support this the loading="lazy" is the easiest option:
<video loading="lazy" autoplay muted loop playsinline>
<source src="one-does-not-simply.webm" type="video/webm">
<source src="one-does-not-simply.mp4" type="video/mp4">
</video>
To support lazy-loading of video in other browsers you need to use JavaScript and modify your <video> markup accordingly:
<video class="lazy" preload="none" controls muted loop playsinline width="610" height="254" poster="one-does-not-simply.jpg">
<source src="one-does-not-simply.webm" type="video/webm">
<source src="one-does-not-simply.mp4" type="video/mp4">
</video>
You'll notice the removal of autoplay, and the addition of the class="lazy", preload="none" and poster attributes, which lets you specify a placeholder to occupy the <video> element's space until the video is lazy-loaded. Then, we use a JavaScript Intersection Observer to re-enabled the autoplaying:
document.addEventListener("DOMContentLoaded", function() {
var lazyVideos = [].slice.call(document.querySelectorAll("video.lazy"));
if ("IntersectionObserver" in window) {
var lazyVideoObserver = new IntersectionObserver(function(entries, observer) {
entries.forEach(function(video) {
if (video.isIntersecting) {
video.target.removeAttribute('preload');
video.target.removeAttribute('controls');
video.target.autoplay = true;
video.target.classList.remove("lazy");
lazyVideoObserver.unobserve(video.target);
}
});
});
lazyVideos.forEach(function(lazyVideo) {
lazyVideoObserver.observe(lazyVideo);
});
}
});
Using this method, the video does not autoplay by default and only when the intersection observer detects the video as entering the viewport. Additionally, the controls attribute handles the cases when the JavaScript fails. Together this means you have a video solution that emulates animated GIF behavior, but doesn't incur the same intensive data usage as animated GIFs do, and you can lazy-load that content.
Lazy loading libraries
The following libraries can help you to lazy-load video:
- vanilla-lazyload and lozad.js are super lightweight options that use Intersection Observer only. As such, they are highly performant, but will need to be polyfilled before you can use them on older browsers.
- If you need a React-specific lazy loading library, you might consider react-lazyload. While it doesn't use Intersection Observer, it does provide a familiar method of lazy loading images for those accustomed to developing applications with React.
Each of these lazy loading libraries is well documented, with plenty of markup patterns for your various lazy loading endeavors.
As the loading attribute for <video> becomes available in more browsers we expect JavaScript solutions (including libraries) to be used less in favor of the built-in solutions, except for those that want more control than that allows.