Nov 27, 2023
Lite YouTube Embed for Svelte
I’m a fan of Paul Irish’s Lite YouTube Embed library for loading YouTube video embeds on-demand.
In late 2022, I ported the library’s code over to the Svelte component shown below. Please feel free to copy it and drop it into your Svelte projects, if it’s helpful.
The Component
This Svelte component can be saved as LiteYouTubeEmbed.svelte
:
<script lang="ts">
export let videoId: string;
export let playLabel = 'Play';
export let params = '';
export let posterImageSrc = '';
let activated = false;
let hovered = false;
$: videoId, (activated = false);
$: computedParams = (() => {
const p = new URLSearchParams(params);
p.append('autoplay', '1');
return p.toString();
})();
function focus(node: HTMLElement) {
node.focus();
}
</script>
<svelte:head>
<link rel="preconnect" href="https://i.ytimg.com" />
{#if hovered}
<link rel="preconnect" href="https://www.youtube-nocookie.com" />
<link rel="preconnect" href="https://www.google.com" />
{/if}
</svelte:head>
<div
class="lite-youtube"
class:lite-youtube-activated={activated}
on:pointerover|once={() => (hovered = true)}
on:click={() => (activated = true)}
on:keypress={() => (activated = true)}
role="button"
tabindex="0"
>
{#key videoId}
<picture>
<img class="lite-youtube-poster" alt={playLabel} src={posterImageSrc} />
</picture>
{/key}
<button type="button" class="lite-youtube-playbtn" aria-label={playLabel} />
{#if activated}
<iframe
width="560"
height="315"
title={playLabel}
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
src="https://www.youtube-nocookie.com/embed/{encodeURIComponent(videoId)}?{computedParams}"
use:focus
/>
{/if}
</div>
<style global>
:global(.lite-youtube) {
background-color: #000000;
position: relative;
display: block;
contain: content;
cursor: pointer;
}
/* gradient */
:global(.lite-youtube::before) {
content: '';
display: block;
position: absolute;
top: 0;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==);
background-position: top;
background-repeat: repeat-x;
height: 60px;
padding-bottom: 50px;
width: 100%;
transition: all 0.2s cubic-bezier(0, 0, 0.2, 1);
box-sizing: unset;
z-index: 1;
}
/* responsive iframe with a 16:9 aspect ratio
thanks https://css-tricks.com/responsive-iframes/
*/
:global(.lite-youtube::after) {
content: '';
display: block;
padding-bottom: calc(100% / (16 / 9));
}
:global(.lite-youtube) > :global(iframe) {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
border: 0;
}
/* poster */
:global(.lite-youtube-poster) {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
object-fit: cover;
}
/* play button */
:global(.lite-youtube) > :global(.lite-youtube-playbtn) {
width: 68px;
height: 48px;
position: absolute;
cursor: pointer;
transform: translate3d(-50%, -50%, 0);
top: 50%;
left: 50%;
z-index: 1;
background-color: transparent;
/* YT's actual play button svg */
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 68 48"><path fill="%23f00" fill-opacity="0.8" d="M66.52,7.74c-0.78-2.93-2.49-5.41-5.42-6.19C55.79,.13,34,0,34,0S12.21,.13,6.9,1.55 C3.97,2.33,2.27,4.81,1.48,7.74C0.06,13.05,0,24,0,24s0.06,10.95,1.48,16.26c0.78,2.93,2.49,5.41,5.42,6.19 C12.21,47.87,34,48,34,48s21.79-0.13,27.1-1.55c2.93-0.78,4.64-3.26,5.42-6.19C67.94,34.95,68,24,68,24S67.94,13.05,66.52,7.74z"></path><path d="M 45,24 27,14 27,34" fill="%23fff"></path></svg>');
filter: grayscale(100%);
transition: filter 0.1s cubic-bezier(0, 0, 0.2, 1);
border: none;
outline: 0;
}
:global(.lite-youtube:hover) > :global(.lite-youtube-playbtn),
:global(.lite-youtube) :global(.lite-youtube-playbtn:focus) {
filter: none;
}
/* Post-click styles */
:global(.lite-youtube.lite-youtube-activated) {
cursor: unset;
}
:global(.lite-youtube.lite-youtube-activated::before),
:global(.lite-youtube.lite-youtube-activated) > :global(.lite-youtube-playbtn) {
opacity: 0;
pointer-events: none;
}
</style>
Usage
You can use it like this:
<script>
import LiteYouTubeEmbed from '$lib/components/LiteYouTubeEmbed.svelte';
</script>
<LiteYouTubeEmbed
videoId="P9AkLbt80_c"
playLabel="Tech CEOs Rank Web Browsers"
posterImageSrc="/video-poster-image.png"
params="modestbranding=1"
/>
You can update the posterImageSrc="/video-poster-image.png"
attribute to point to the image you’d like to use for the video’s facade. For SvelteKit projects, this file can be saved to the static
folder, like this: /static/video-poster-image.png
.
Just like Lite YouTube Embed, all code in this blog post is licensed under the Apache License, Version 2.0.
Since I wrote this code, this port of Lite YouTube Embed to Svelte has been created, as well: https://www.npmjs.com/package/svelte-lite-youtube-embed