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!