You've probably embedded YouTube and Vimeo-videos dozens of times, using the standard <iframe>
-embed-code.
While this works great out-of-the-box, services like Youtube loads a lot of Javascript, even if your users do not click on the embedded video.
I did a test with 3 embedded YouTube-videos. In this case, more than one megabyte of extra data was loaded.
That's a lot of unnecessary bandwidth. We can do better!
Lite Loaders
There are a lot of "lite loaders" out there, like lite-youtube or lite-youtube-embed.
If you're in a hurry, just npm install
one of these.
Otherwise, hang on — it can be even lighter!
The Manual Way
We want to prevent YouTube or Vimeo loading anything, so we'll add the thumbnail-image for the video manually.
YouTube has an image-service, ytimg.com
:
<img loading="lazy" src="https://i.ytimg.com/vi/[VIDEOID]/hqdefault.jpg">
The important part here is the [VIDEOID]. Replace this with the actual id.
Vimeo does not have a service like this, but we can use the (free for now) Vumbnail-service:
<img loading="lazy" src="vumbnail.com/[VIDEOID].jpg">
Vumbnail detects the video-provider automatically from the id/src.
Cool! Now, let's add a wrapper around the image, where we'll also add a dummy <iframe>
and a play-<button>
:
<youtube-embed>
<img loading="lazy" src="https://i.ytimg.com/vi/[VIDEOID]/hqdefault.jpg" alt="Video Description">
<iframe allow="autoplay" src="" data-src="https://www.youtube.com/embed/[VIDEOID]?autoplay=1"></iframe>
<button aria-label="Play video"></button>
</youtube-embed>
It does not have to be a custom element, it's just a bit more readable, and we can use <vimeo-embed>
for Vimeo-videos.
Styling
For styling, we're going to use our custom elements tags directly, instead of class
’es:
:is(vimeo-embed, youtube-embed) {
aspect-ratio: 16 / 9;
border-radius: var(--video-embed-bdrs, 0.25em);
display: grid;
inline-size: 100%;
position: relative;
}
:is(vimeo-embed, youtube-embed) :is(iframe, img) {
block-size: 100%;
border: 0;
border-radius: inherit;
inline-size: 100%;
inset: 0;
object-fit: cover;
position: absolute;
}
For the play-button, we'll add a bunch of CSS Custom Properties — allowing us to easily change colors etc:
:is(vimeo-embed, youtube-embed) button {
background-color: var(--button-bgc, #F00);
block-size: var(--button-h, 50px);
border: 0;
border-radius: var(--button-bdrs, 14%);
display: grid;
inline-size: var(--button-w, 75px);
opacity: var(--button-op, 0.8);
position: absolute;
place-self: center;
transition: all .2s ease-in;
}
:is(vimeo-embed, youtube-embed) button::before {
aspect-ratio: 1;
background: #FFF;
block-size: 1.5em;
clip-path: polygon(20% 0%, 20% 100%, 100% 50%);
content: '';
place-self: center;
}
vimeo-embed button { --button-bgc: #00adef; }
To hide the play-button when the <iframe>
is loaded, we'll check if the src
-attribute contains something else than an empty string:
:is(vimeo-embed, youtube-embed) iframe:not([src=""]) + button {
display: none;
}
Now, to load the real <iframe>
, all we have to do is replace src
with data-src
:
document.querySelectorAll(':is(vimeo-embed, youtube-embed) button').forEach(button => button.addEventListener('click', () => {
const video = button.previousElementSibling;
video.src = video.dataset.src;
}))
And that's it! We've replaced one megabyte of data with a few bytes.
Here's a Codepen-demo, with a short disclaimer: The videos don't autoplay directly from the Codepen-iframe, but will on your own site:
The Javascript Way
If you don't want to handle all that markup, a simpler way could be:
<youtube-embed id="5b4YcLB4DVI" title="Text">
<vimeo-embed id="70591644" title="Text">
All we need is the id
of the video and a title
for the thumbnail alt
-attribute. Then in JavaScript, we can create the <img>
and <button>
-elements:
document.querySelectorAll('vimeo-embed, youtube-embed').forEach(v => {
const [poster, src] = v.tagName === 'VIMEO-EMBED' ?
[`vumbnail.com/${v.id}.jpg`, 'player.vimeo.com/video'] :
[`i.ytimg.com/vi/${v.id}/hqdefault.jpg`, 'www.youtube.com/embed'];
v.innerHTML = `<img loading="lazy" src="https://${poster}" alt="${v.title}"><button aria-label="Play"></button>`;
v.children[1].addEventListener('click', () => v.innerHTML = `<iframe allow="autoplay" src="https://${src}/${v.id}?autoplay=1"></iframe>`)
})
That's 335 bytes gzipped, so it's not going to break your performance-budget.
Happy coding!
Top comments (6)
Creator of Vumbnail here.
Thanks for the shout-out! I also made a handy embed builder for those who want to do it without JavaScript; however, autoplay doesn't work that well.
vumbnail.com/embed-builder
I was going to look into this soon as well! I saw this article on lazy loading youtube vidoeos recently, How to lazy load YouTube videos with vanilla JavaScript , and wanted to investigate it.
One thing that people forget or dont know is that you can lazy load an
iframe
withloading=lazy
(Firefox is a laggard in support however and Safari has it behind a flag). This will spare you the upfront cost of loading the video scripts and give a faster page load if you do have to go theiframe
route.Yes, good point! It's only Chrome for now, but Safari has lazy-load for iframes behind a flag in v16. Firefox status is unknown.
Yep. Let's hope Firefox follows suit soon.
Considering so many embedded videos are sourced from youtube, it is an important one IMO. I just try to avoid YouTube altogether.
I wrote about lazy-loading videos a while back, you can use
preload="none"
and the video file is only downloaded when the user hits play.Native lazy-loading - Why doesn't the video element have it?
Rob OLeary ・ Mar 28 ・ 4 min read
One question, is it still possible to trigger Autoplay (to omit the double clicking)? I think I've read some time ago that this is prevent by modern browsers now, right?
It works locally, but not from the Codepen (the small "disclaimer"). Maybe because Codepen embed is in itself an iframe? Not sure!