Creating click-to-play YouTube videos in JS and CSS that don’t load anything until they’re needed

Let's be honest: streaming video is kinda hard. If you want to embed a video on your website, you're going to need it in multiple formats to support all the major browsers, and you'll probably want each of those in multiple resolutions too so your visitors with slower connections or less powerful devices aren't left out in the cold.

You can always roll your own native HTML5 player with a bit of messing about in ffmpeg and a DASH manifest, or go ready-made and embed JWPlayer or Video.js. Of course, since video can be pretty heavy, you might want to host the files from a CDN too.

But I just want a simple little website for my personal portfolio, and since I don't expect many visitors, it's just not worth the effort. I'm not the biggest Google fan but it's undeniable that YouTube have built a very competent platform, and it's very tempting to just throw a couple iframes up and call it a day. But my website is lightweight and fast (and I feel smug about it): it doesn't need to pull in any external resources, and I don't want Google tracking all of my visitors before they've even watched a video. With a few simple changes, we can make our embeds only load when they're clicked, and give them nice thumbnails and buttons to boot.

We start by creating our placeholder player:

<div class="youtube overlay" data-id="xi7U1afxMQY">
	<a class="play" href="https://youtube.com/watch?v=xi7U1afxMQY" aria-label="Play video">
		<div class="thumbnail-container">
			<picture>
				<source srcset="thumbnails/mountains.avif 960w, thumbnails/mountains-2x.avif 1920w" type="image/avif">
				<img class="thumbnail" srcset="thumbnails/mountains.jpg 960w, thumbnails/mountains-2x.jpg 1920w" src="thumbnails/mountains.jpg" alt="Life in the Mountains" loading="lazy">
			</picture>
			<span class="duration">8:48</span>
			<div class="play-overlay"></div>
		</div>
	</a>
</div>
Code language: HTML, XML (xml)

The ID of the video is stored in the data-id attribute, which we'll use later to insert the iframe. Since we'll need Javascript for this, the play link contains the full URL so non-JS users can click through to watch it directly on YouTube. We include a thumbnail, in JPG for compatibility and AVIF for better compression on modern browsers (avif.io is a great little online tool to convert all of your images, since as I write this it's rarely supported by image editors), and in two resolutions (960px and 1920px) as smaller screens don't need the full-size image. We also include the duration – why not? – and play-overlay will hold a play button icon.

We can now apply some CSS:

.overlay {
	position: relative;
	width: 100vw;
	height: calc((100vw/16)*9);
	max-width: 1920px;
	max-height: 1080px;
}

.overlay .thumbnail-container {
	position: relative;
}

.overlay .thumbnail {
	display: block;
}

.overlay .duration {
	position: absolute;
	z-index: 2;
	right: 0.5rem;
	bottom: 0.5rem;
	padding: 0.2rem 0.4rem;
	background-color: rgba(0, 0, 0, 0.6);
	color: white;
}

.overlay .play-overlay {
	position: absolute;
	z-index: 1;
	top: 0;
	width: 100%;
	height: 100%;
	background: rgba(0, 0, 0, 0.1) url("images/arrow.svg") no-repeat scroll center center / 3rem 3rem;
	transition: background-color 0.7s;
}

.overlay .play-overlay:hover {
	background-color: rgba(0, 0, 0, 0);
}

.overlay iframe {
	position: absolute;
	z-index: 3;
	width: 100%;
	height: 100%;
}
Code language: CSS (css)

On my site I've already set the width and height for the video's container, so I've just shown an example for overlay here, using vw units so it fills the viewport's width whether portrait or landscape. My thumbnails only go up to 1920x1080 so I've limited it to that in this example. Sorry 4K users! You can use a calc expression for the height to get the correct aspect ratio (here 16:9).

On to positioning. Setting position: relative for the container means we can use absolute positioning for the iframe to fit to the thumbnail's size, and position: relative on the thumbnail's container and display: block on the thumbnail itself fits everything else to the thumbnail too. Duration sits in the bottom right with a little space to breathe. We set z-indexes so elements will stack in the correct order: thumbnail on the bottom, overlay above it, duration on top of that, and the iframe will cover everything once it's added.

What remains is just little extras: the overlay slightly darkens the thumbnail until it's hovered over, and we take advantage of the background property allowing both colour and URL to drop a play button on top. The button is an SVG so simple you can paste the code into arrow.svg yourself:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><polygon points="0 0 100 50 0 100 0 0" style="fill:#fff"/></svg>Code language: HTML, XML (xml)

Now all we need is a little JS to handle inserting the iframe when the placeholder is clicked – no JQuery required! Insert it just before the closing </body> tag so it runs once all the placeholders it'll be working on have loaded.

document.querySelectorAll(".youtube").forEach(function(element) {
	element.querySelector(".play").addEventListener("click", (event) => {
		event.preventDefault();
		loadVideo(element);
	});
});
var loadVideo = function(element) {
	var iframe = document.createElement("iframe");
	iframe.setAttribute("src", "https://www.youtube.com/embed/" + element.getAttribute("data-id") + "?autoplay=1");
	iframe.setAttribute("frameborder", "0");
	iframe.setAttribute("allowfullscreen", "1");
	iframe.setAttribute("allow", "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture");
	element.insertBefore(iframe, element.querySelector(".play"));
};
Code language: JavaScript (javascript)

The first function finds every placeholder on the page, adding a listener for each play button being clicked. Note that we use the overlay class for the CSS but youtube for the JS – this is so we can extend our code later to cover more platforms if we like, which would need different JS. When a visitor clicks play, it cancels the default action (navigating to the URL, which we included for non-JS users) and calls the loadVideo function, passing on the specific video they clicked.

The loadVideo function puts together the iframe for the video embed, getting the ID from the container's data-id attribute. We use www.youtube-nocookie.com (the www is necessary!) as it pinkie promises not to set cookies until you play the video (2022 edit - this URL seems to be commonly blocked by adblockers, so I've reverted it to the standard one), and set a few attributes to let mobile users rotate the screen, copy the link to their clipboard etc. Although we set it to autoplay since we've already clicked on the placeholder, it doesn't seem to work as I write this. I'm not sure why and they encourage you to embed their JS API instead, but that would sort of defeat the point. Finally, it inserts the iframe as the first element in the container, where it covers up the rest.

If all goes well, you should now have something that looks like this (albeit functional):

Completed placeholder for click-to-play video

You can also see it in action on my website. Thanks for reading!

3 thoughts on “Creating click-to-play YouTube videos in JS and CSS that don’t load anything until they’re needed

  1. Pingback: Building a loopable slider/carousel for my portfolio in vanilla JS and CSS | asdfghjkl;'#

    1. asdfghjkl Post author

      Sorry for the slow reply! I realised I made a mistake on my own website in the pursuit of optimisation that lost the video ID. Fixed that now. I also noticed that youtube-nocookie.com seems to be commonly blocked by adblockers, so I’ve updated the post to use the standard youtube URL. Hope that works for you!

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.