DEV3 Technologies home

Lazy-loading YouTube facade

Show a thumbnail and play button instead of an iframe, and swap in the real embed only when the visitor clicks.

WordPressUtilitiesyoutubelazy-loadingperformanceembed

Live preview

Code

/**
 * Replaces [data-yt] buttons with a youtube-nocookie iframe on first click.
 * Markup: <button type="button" class="yt-facade" data-yt="VIDEO_ID" data-title="Video title"></button>
 */
(() => {
	const ID = /^[\w-]{11}$/; // YouTube ids are 11 safe characters.

	function mount(btn) {
		const id = btn.dataset.yt;
		if (!ID.test(id || '')) return;

		const title = btn.dataset.title || 'Video';
		btn.style.backgroundImage = `url(https://i.ytimg.com/vi/${id}/hqdefault.jpg)`;
		btn.setAttribute('aria-label', `Play video: ${title}`);

		btn.addEventListener('click', () => {
			const frame = document.createElement('iframe');
			frame.src = `https://www.youtube-nocookie.com/embed/${id}?autoplay=1&rel=0`;
			frame.title = title;
			frame.allow = 'autoplay; encrypted-media; picture-in-picture';
			frame.allowFullscreen = true;
			frame.loading = 'lazy';
			btn.replaceWith(frame);
			frame.focus();
		}, { once: true });
	}

	// Only set up thumbnails near the viewport.
	const io = new IntersectionObserver((entries) => {
		entries.forEach((entry) => {
			if (entry.isIntersecting) { mount(entry.target); io.unobserve(entry.target); }
		});
	}, { rootMargin: '200px' });

	document.querySelectorAll('.yt-facade[data-yt]').forEach((el) => io.observe(el));
})();

How it works

What it solves

A single YouTube iframe downloads several hundred kilobytes of script before anyone presses play, and it sets tracking cookies. A facade shows a lightweight image and loads the real player only on demand, which helps page speed and privacy.

How it works

Each embed is a button with a video id. When it approaches the viewport, an observer sets its thumbnail and an accessible label. The id is checked against an eleven character pattern so a crafted attribute cannot inject a URL. On click, the button is replaced by an iframe on the privacy-enhanced domain with autoplay, a title and the correct permissions, and focus moves to it.

CSS draws the play icon with pseudo-elements, so no image is needed.

Adapting it

In WordPress, filter embed_oembed_html to turn YouTube iframes into these buttons, or output them from a shortcode. Add a data-start option by appending start= to the URL after validating it as an integer. Use maxresdefault.jpg for high resolution thumbnails when you know they exist.

Pitfalls

  • Without JavaScript the button does nothing; consider a visible link to the video as fallback.
  • The thumbnail is a request to a third party, so mention it in privacy notes.
  • Keep the aspect ratio on the wrapper to avoid layout shift.
  • Autoplay works because the click is a user gesture.

How to use it

Include both files on pages with videos.

Add <button type="button" class="yt-facade" data-yt="abcdefghijk" data-title="Intro"></button> where the video should be.

Test keyboard activation with Tab and Enter.

Licence: GPL-2.0-or-later. This snippet comes from WordPress code and is GPL-2.0-or-later.

Related snippets