How to embed YouTube and Vimeo the light way
Tags: html, javascript, webdev, tutorial
Published: 2022-06-25 09:56:49

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](https://github.com/justinribeiro/lite-youtube) or [lite-youtube-embed](https://github.com/paulirish/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`:

```html
<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](https://vumbnail.com)-service:

```html
<img loading="lazy" src="vumbnail.com/[VIDEOID].jpg">
```

[Vumbnail](https://vumbnail.com) 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>`:


```html
<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:

```css
: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:

```css
: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:


```css
: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`:

```js
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:_

{% codepen https://codepen.io/stoumann/pen/BaYgvre %}

---

## The Javascript Way
If you don't want to handle all that markup, a simpler way could be:

```html
<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:

```js
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!