A Pen by Chris Coyier on CodePen.
Created
December 18, 2019 14:54
-
-
Save bishoplee/2f803499ab22d7b51795abd40a906e48 to your computer and use it in GitHub Desktop.
lite-youtube
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<lite-youtube videoid="ogfYd705cRs" style="background-image: url('https://i.ytimg.com/vi/ogfYd705cRs/hqdefault.jpg');"> | |
<div class="lty-playbtn"></div> | |
</lite-youtube> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* A lightweight youtube embed. Still should feel the same to the user, just MUCH faster to initialize and paint. | |
* | |
* Thx to these as the inspiration | |
* https://storage.googleapis.com/amp-vs-non-amp/youtube-lazy.html | |
* https://autoplay-youtube-player.glitch.me/ | |
* | |
* Once built it, I also found these: | |
* https://github.com/ampproject/amphtml/blob/master/extensions/amp-youtube (👍👍) | |
* https://github.com/Daugilas/lazyYT | |
* https://github.com/vb/lazyframe | |
*/ | |
class LiteYTEmbed extends HTMLElement { | |
constructor() { | |
super(); | |
// Gotta encode the untrusted value | |
// https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#rule-2---attribute-escape-before-inserting-untrusted-data-into-html-common-attributes | |
this.videoId = encodeURIComponent(this.getAttribute('videoid')); | |
/** | |
* Lo, the youtube placeholder image! (aka the thumbnail, poster image, etc) | |
* There is much internet debate on the reliability of thumbnail URLs. Weak consensus is that you | |
* cannot rely on anything and have to use the YouTube Data API. | |
* | |
* amp-youtube also eschews using the API, so they just try sddefault with a hqdefault fallback: | |
* https://github.com/ampproject/amphtml/blob/6039a6317325a8589586e72e4f98c047dbcbf7ba/extensions/amp-youtube/0.1/amp-youtube.js#L498-L537 | |
* For now I'm gonna go with this confident (lol) assertion: https://stackoverflow.com/a/20542029, though I'll use `i.ytimg` to optimize for origin reuse. | |
* | |
* Worth noting that sddefault is _higher_ resolution than hqdefault. Naming is hard. ;) | |
* From my own testing, it appears that hqdefault is ALWAYS there sddefault is missing for ~10% of videos | |
* | |
* TODO: Do the sddefault->hqdefault fallback | |
* - When doing this, apply referrerpolicy (https://github.com/ampproject/amphtml/pull/3940) | |
* TODO: Consider using webp if supported, falling back to jpg | |
*/ | |
this.posterUrl = `https://i.ytimg.com/vi/${this.videoId}/hqdefault.jpg`; | |
// Warm the connection for the poster image | |
LiteYTEmbed.addPrefetch('preload', this.posterUrl, 'image'); | |
// TODO: support dynamically setting the attribute via attributeChangedCallback | |
} | |
connectedCallback() { | |
this.style.backgroundImage = `url("${this.posterUrl}")`; | |
const playBtn = document.createElement('div'); | |
playBtn.classList.add('lty-playbtn'); | |
this.append(playBtn); | |
// On hover (or tap), warm up the TCP connections we're (likely) about to use. | |
this.addEventListener('pointerover', LiteYTEmbed.warmConnections, {once: true}); | |
// Once the user clicks, add the real iframe and drop our play button | |
// TODO: In the future we could be like amp-youtube and silently swap in the iframe during idle time | |
// We'd want to only do this for in-viewport or near-viewport ones: https://github.com/ampproject/amphtml/pull/5003 | |
this.addEventListener('click', e => this.addIframe()); | |
} | |
// // TODO: Support the the user changing the [videoid] attribute | |
// attributeChangedCallback() { | |
// } | |
/** | |
* Add a <link rel={preload | preconnect} ...> to the head | |
*/ | |
static addPrefetch(kind, url, as) { | |
const linkElem = document.createElement('link'); | |
linkElem.rel = kind; | |
linkElem.href = url; | |
if (as) { | |
linkElem.as = as; | |
} | |
linkElem.crossorigin = true; | |
document.head.append(linkElem); | |
} | |
/** | |
* Begin pre-connecting to warm up the iframe load | |
* Since the embed's network requests load within its iframe, | |
* preload/prefetch'ing them outside the iframe will only cause double-downloads. | |
* So, the best we can do is warm up a few connections to origins that are in the critical path. | |
* | |
* Maybe `<link rel=preload as=document>` would work, but it's unsupported: http://crbug.com/593267 | |
* But TBH, I don't think it'll happen soon with Site Isolation and split caches adding serious complexity. | |
*/ | |
static warmConnections() { | |
if (LiteYTEmbed.preconnected) return; | |
// The iframe document and most of its subresources come right off youtube.com | |
LiteYTEmbed.addPrefetch('preconnect', 'https://www.youtube.com'); | |
// The botguard script is fetched off from google.com | |
LiteYTEmbed.addPrefetch('preconnect', 'https://www.google.com'); | |
// Not certain if these ad related domains are in the critical path. Could verify with domain-specific throttling. | |
LiteYTEmbed.addPrefetch('preconnect', 'https://googleads.g.doubleclick.net'); | |
LiteYTEmbed.addPrefetch('preconnect', 'https://static.doubleclick.net'); | |
LiteYTEmbed.preconnected = true; | |
} | |
addIframe(){ | |
const iframeHTML = ` | |
<iframe width="560" height="315" frameborder="0" | |
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen | |
src="https://www.youtube.com/embed/${this.videoId}?autoplay=1" | |
></iframe>`; | |
this.insertAdjacentHTML('beforeend', iframeHTML); | |
this.classList.add('lyt-activated'); | |
} | |
} | |
// Register custome element | |
customElements.define('lite-youtube', LiteYTEmbed); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
lite-youtube { | |
background-color: #000; | |
position: relative; | |
display: block; | |
contain: content; | |
background-position: center center; | |
background-size: cover; | |
cursor: pointer; | |
} | |
/* gradient */ | |
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); | |
} | |
/* responsive iframe with a 16:9 aspect ratio | |
thanks https://css-tricks.com/responsive-iframes/ | |
*/ | |
lite-youtube::after { | |
content: ""; | |
display: block; | |
padding-bottom: calc(100% / (16 / 9)); | |
} | |
lite-youtube > iframe { | |
width: 100%; | |
height: 100%; | |
position: absolute; | |
top: 0; | |
left: 0; | |
} | |
/* play button */ | |
lite-youtube > .lty-playbtn { | |
width: 70px; | |
height: 46px; | |
background-color: #212121; | |
z-index: 1; | |
opacity: 0.8; | |
border-radius: 14%; /* TODO: Consider replacing this with YT's actual svg. Eh. */ | |
transition: all 0.2s cubic-bezier(0, 0, 0.2, 1); | |
} | |
lite-youtube:hover > .lty-playbtn { | |
background-color: #f00; | |
opacity: 1; | |
} | |
/* play button triangle */ | |
lite-youtube > .lty-playbtn::before { | |
content: ''; | |
border-style: solid; | |
border-width: 11px 0 11px 19px; | |
border-color: transparent transparent transparent #fff; | |
} | |
lite-youtube > .lty-playbtn, | |
lite-youtube > .lty-playbtn::before { | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
transform: translate3d(-50%, -50%, 0); | |
} | |
/* Post-click styles */ | |
lite-youtube.lyt-activated { | |
cursor: unset; | |
} | |
lite-youtube.lyt-activated::before, | |
lite-youtube.lyt-activated > .lty-playbtn { | |
opacity: 0; | |
pointer-events: none; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment