Video and Embed Craft
When to Use
You need to embed third-party videos (YouTube, Vimeo), self-hosted video, or video-as-background. Each scenario has distinct performance implications and autoplay policy constraints.
Decision
| If you need... | Use... | Why |
|---|---|---|
| YouTube/Vimeo embed, performance matters | Facade pattern (lite-youtube-embed or custom) |
Saves ~500KB initial load; 3–5x better LCP |
| YouTube/Vimeo, simplicity over performance | Native iframe with loading="lazy" |
Still defers load; worse than facade but better than eager |
| Self-hosted video, decorative background | <video autoplay muted loop playsinline> |
Required attributes for cross-browser mobile autoplay |
| Self-hosted video, user-controlled | <video controls poster="thumb.jpg"> |
Proper UX; accessibility-compliant |
| Replacing an animated GIF | <video autoplay muted loop playsinline> |
5–20x smaller; hardware-decoded |
YouTube Facade Pattern
Load a static thumbnail image styled to look like the player. On click, swap in the real iframe.
<div class="video-facade" data-video-id="dQw4w9WgXcQ" style="aspect-ratio: 16/9;">
<img
src="https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg"
alt="Video: [Descriptive title]"
loading="lazy"
width="1280"
height="720"
>
<button class="play-btn" aria-label="Play video: [Descriptive title]">▶</button>
</div>
document.querySelectorAll('.video-facade').forEach(facade => {
facade.querySelector('.play-btn').addEventListener('click', () => {
const id = facade.dataset.videoId;
const iframe = document.createElement('iframe');
iframe.src = `https://www.youtube-nocookie.com/embed/${id}?autoplay=1`;
iframe.allow = 'autoplay; encrypted-media; picture-in-picture';
iframe.allowFullscreen = true;
facade.replaceWith(iframe);
});
});
Use youtube-nocookie.com to reduce tracking cookies. For a pre-built implementation, see lite-youtube-embed (224x faster than native embed per Lighthouse tests).
Video Autoplay Policies
| Browser | Autoplay behavior |
|---|---|
| Chrome | Muted autoplay always allowed; unmuted requires Media Engagement Index score |
| Safari/iOS | Requires muted + playsinline; will not autoplay in Low Power Mode or background tabs |
| Firefox | Muted autoplay allowed; site permissions can allow unmuted |
The universal autoplay formula for background/decorative video:
<video autoplay muted loop playsinline poster="video-poster.jpg">
<source src="video.mp4" type="video/mp4">
<!-- Mobile fallback: static image if video unsupported -->
</video>
All four attributes are required: autoplay (starts video), muted (required for autoplay on mobile), loop (keeps playing), playsinline (prevents iOS fullscreen takeover).
Poster Frame Selection
Never use the first frame as a poster — it is often a blank black frame or an unrepresentative composition. Select a frame at 10–25% through the video that shows the main subject clearly. Generate with FFmpeg:
ffmpeg -i video.mp4 -ss 00:00:02 -frames:v 1 poster.jpg
Poster image should follow the same format/optimization rules as regular images: WebP at 80% quality, explicit width/height.
Video as Background — Performance
Background video is expensive. Mitigate with:
- Max duration: 6–15 seconds looping; longer means slower initial load
- Resolution: 1080p maximum; 720p often sufficient for backgrounds
- Codec: H.264 MP4 for broadest support; add WebM/VP9 for 20–30% smaller files on supporting browsers
- Mobile fallback: Detect prefers-reduced-motion and show a static image instead
- File size budget: < 3MB for a background video loop; aim for < 1.5MB
@media (prefers-reduced-motion: reduce) {
.bg-video { display: none; }
.bg-fallback { display: block; }
}
Accessibility
- Captions: All videos with speech or meaningful audio MUST have captions (
<track kind="captions">) - Audio descriptions: Videos where visual content conveys meaning not in audio need audio descriptions
- Decorative background video: Must be
muted; should havearia-hidden="true"on the<video>element - Transcript: Provide a linked transcript for longer informational videos
- Play/pause control: Background videos that run for more than 5 seconds need a user control to pause — WCAG 2.2 guideline 2.2.2
Common Mistakes
- Eager-loading YouTube iframes — adds 500KB+ and multiple origin connections to initial page load
- Using
autoplaywithoutmutedand expecting it to work on mobile — all mobile browsers block unmuted autoplay - Missing
playsinlineon iOS — video opens in fullscreen player, breaking layout - Not providing a
poster— blank black frame shows while video buffers, looks broken - Autoplaying video without a pause control — violates WCAG 2.2.2 (Pause, Stop, Hide)
See Also
- Image Format Strategy — animated WebP vs
<video>decision - Placeholder Strategies — poster images and aspect-ratio preservation
- Reference: MDN Autoplay Guide
- Reference: web.dev Third-Party Embed Best Practices
- Reference: lite-youtube-embed