If you’re a content creator or work on a site that heavily relies on YouTube embeds, you’ll quickly realize that they can dramatically impact the load time of a page. Load times can also grow depending on the number of YouTube embeds on a page.
In this post, we’ll see a technique to reduce page load times by using newer HTML and JavaScript techniques to load videos when the user needs them.
Let’s get started.
Why Are YouTube Embeds Slowing My Page Load Times
YouTube videos typically have a thumbnail image displayed to the user before the video starts. YouTube may serve up to four thumbnails, all varying in quality. Unfortunately for performance, most browsers treat images as blocking elements on a page. Regarding performance, this means the page cannot complete loading unless the element is also loaded.
There have been recent techniques to mitigate this issue, including adding a width
and height
to images so the client can calculate the layout in advance of the image loading and the use of the lazy
attribute to signify that you’re willing to load the asset outside of the critical rendering path. That’s great, right?!
Well, YouTube embeds utilize an iframe
where you don’t have control over the markup or attributes. So how do you get around this issue? You cheat… sort of.
Using Plyr For YouTube Embeds
In my use case, I had over 42 YouTube images on a page, with only one visible at a time. That’s a lot of image requests to YouTube’s thumbnail service, making my page understandably slow to load.
My first step was to use a video player other than that of YouTube’s iframe embed. In my case, I used Plyr. It’s pretty straightforward to use the library with existing YouTube content.
<div class="video-player"
data-plyr-provider="vimeo"
data-plyr-embed-id="76979871"></div>
With the HTML in place, you must create an instance of the Plyr
JavaScript class for each occurrence of an embedded video.
const players = Array
.from(document.querySelectorAll('.video-player'))
.map((p) => new Plyr(p));
Note that this suffers from the same issue as before, so we need to be smarter here. You’ll see how to solve this same issue in the next section.
Using IntersectionObserver To Create Plyr Instances
Our goal is only to instantiate Plyr
instances that the user sees. We can accomplish this using the IntersectionObserver
type.
The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document’s viewport. –Mozilla
For our use case, we’ll monitor our video elements’ visibility and call new Plyr
once.
// import Plyr dependency
import Plyr from "plyr";
// simplified onVisible method
const onVisible = function (element, callback) {
const options = {
root: document,
};
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
callback(entry, observer);
});
}, options);
observer.observe(element);
};
// create an array of video player HTML elements
const videos = Array.from(document.querySelectorAll(".video-player"));
// apply onVisible callback
videos.forEach((video) => {
onVisible(video, (entry, observer) => {
if (entry.intersectionRatio > 0) {
new Plyr(entry.target);
observer.unobserve(entry.target);
}
});
});
The vital part of this code is the implementation of the callback for each element.
if (entry.intersectionRatio > 0) {
new Plyr(entry.target);
observer.unobserve(entry.target);
}
Once we create the video player, we no longer need to observe its changes, as we’ve loaded all assets, and the video is ready for the user. Now videos are created when the user needs them, not on the initial page load.
Using the IntersectionObserver
technique, my page load times went from several seconds to milliseconds. In addition, I improved performance and kept users happy with just a few lines of JavaScript.
Conclusion
Web development is easy to get into but takes a lifetime to master, especially as the APIs and features of HTML, JavaScript, and CSS improve. Also, when working with third parties, finding solutions around their implementations can be challenging. Luckily, you can always load things when needed rather than all at once. This technique can also be used for other network-heavy and blocking resources.
I hope you found this post interesting and helpful. As always, thank you for reading.