vendor: 11,735 bytes, lines 1-353
1/** 2 * 3 * The shadowDom / Intersection Observer version of Paul's concept: 4 * https://github.com/paulirish/lite-youtube-embed 5 * 6 * A lightweight YouTube embed. Still should feel the same to the user, just 7 * MUCH faster to initialize and paint. 8 * 9 * Thx to these as the inspiration 10 * https://storage.googleapis.com/amp-vs-non-amp/youtube-lazy.html 11 * https://autoplay-youtube-player.glitch.me/ 12 * 13 * Once built it, I also found these (ðð): 14 * https://github.com/ampproject/amphtml/blob/master/extensions/amp-youtube 15 * https://github.com/Daugilas/lazyYT https://github.com/vb/lazyframe 16 */ 17/* 18 * Vimeo example embed markup: 19<iframe src="https://player.vimeo.com/video/364402896" 20 width="640" height="360" 21 frameborder="0" 22 allow="autoplay; fullscreen" allowfullscreen> 23</iframe> 24<p><a href="https://vimeo.com/364402896"> 25 Alex Russell - The Mobile Web: MIA</a> from 26 <a href="https://vimeo.com/fronteers">Fronteers</a> 27 on <a href="https://vimeo.com">Vimeo</a>. 28</p> 29 */ 30let LiteVimeoEmbed = /** @class */ (() => { 31 class LiteVimeoEmbed extends HTMLElement { 32 constructor() { 33 super(); 34 this.iframeLoaded = false; 35 this.setupDom(); 36 } 37 static get observedAttributes() { 38 return ["videoid"]; 39 } 40 connectedCallback() { 41 this.addEventListener("pointerover", LiteVimeoEmbed.warmConnections, { 42 once: true 43 }); 44 this.addEventListener("click", () => this.addIframe()); 45 } 46 get videoId() { 47 return encodeURIComponent(this.getAttribute("videoid") || ""); 48 } 49 set videoId(id) { 50 this.setAttribute("videoid", id); 51 } 52 get videoTitle() { 53 return this.getAttribute("videotitle") || "Video"; 54 } 55 set videoTitle(title) { 56 this.setAttribute("videotitle", title); 57 } 58 get videoPlay() { 59 return this.getAttribute("videoPlay") || "Play"; 60 } 61 set videoPlay(name) { 62 this.setAttribute("videoPlay", name); 63 } 64 get videoStartAt() { 65 return this.getAttribute("videoPlay") || "0s"; 66 } 67 set videoStartAt(time) { 68 this.setAttribute("videoPlay", time); 69 } 70 get autoLoad() { 71 return this.hasAttribute("autoload"); 72 } 73 set autoLoad(value) { 74 if (value) { 75 this.setAttribute("autoload", ""); 76 } else { 77 this.removeAttribute("autoload"); 78 } 79 } 80 get autoPlay() { 81 return this.hasAttribute("autoplay"); 82 } 83 set autoPlay(value) { 84 if (value) { 85 this.setAttribute("autoplay", "autoplay"); 86 } else { 87 this.removeAttribute("autoplay"); 88 } 89 } 90 /** 91 * Define our shadowDOM for the component 92 */ 93 setupDom() { 94 const shadowDom = this.attachShadow({ mode: "open" }); 95 shadowDom.innerHTML = ` 96 <style> 97 :host { 98 contain: content; 99 display: block; 100 position: relative; 101 width: 100%; 102 padding-bottom: calc(100% / (16 / 9)); 103 } 104 105 #frame, #fallbackPlaceholder, iframe { 106 position: absolute; 107 width: 100%; 108 height: 100%; 109 } 110 111 #frame { 112 cursor: pointer; 113 } 114 115 #fallbackPlaceholder { 116 object-fit: cover; 117 } 118 119 #frame::before { 120 content: ''; 121 display: block; 122 position: absolute; 123 top: 0; 124 background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==); 125 background-position: top; 126 background-repeat: repeat-x; 127 height: 60px; 128 padding-bottom: 50px; 129 width: 100%; 130 transition: all 0.2s cubic-bezier(0, 0, 0.2, 1); 131 z-index: 1; 132 } 133 /* play button */ 134 .lvo-playbtn { 135 width: 70px; 136 height: 46px; 137 background-color: #212121; 138 z-index: 1; 139 opacity: 0.8; 140 border-radius: 10%; 141 transition: all 0.2s cubic-bezier(0, 0, 0.2, 1); 142 border: 0; 143 } 144 #frame:hover .lvo-playbtn { 145 background-color: rgb(98, 175, 237); 146 opacity: 1; 147 } 148 /* play button triangle */ 149 .lvo-playbtn:before { 150 content: ''; 151 border-style: solid; 152 border-width: 11px 0 11px 19px; 153 border-color: transparent transparent transparent #fff; 154 } 155 .lvo-playbtn, 156 .lvo-playbtn:before { 157 position: absolute; 158 top: 50%; 159 left: 50%; 160 transform: translate3d(-50%, -50%, 0); 161 } 162 163 /* Post-click styles */ 164 .lvo-activated { 165 cursor: unset; 166 } 167 168 #frame.lvo-activated::before, 169 .lvo-activated .lvo-playbtn { 170 display: none; 171 } 172 </style> 173 <div id="frame"> 174 <picture> 175 <source id="webpPlaceholder" type="image/webp"> 176 <source id="jpegPlaceholder" type="image/jpeg"> 177 <img id="fallbackPlaceholder" referrerpolicy="origin"> 178 </picture> 179 <button class="lvo-playbtn"></button> 180 </div> 181 `; 182 this.domRefFrame = this.shadowRoot.querySelector("#frame"); 183 this.domRefImg = { 184 fallback: this.shadowRoot.querySelector("#fallbackPlaceholder"), 185 webp: this.shadowRoot.querySelector("#webpPlaceholder"), 186 jpeg: this.shadowRoot.querySelector("#jpegPlaceholder") 187 }; 188 this.domRefPlayButton = this.shadowRoot.querySelector(".lvo-playbtn"); 189 } 190 /** 191 * Parse our attributes and fire up some placeholders 192 */ 193 setupComponent() { 194 this.initImagePlaceholder(); 195 this.domRefPlayButton.setAttribute( 196 "aria-label", 197 `${this.videoPlay}: ${this.videoTitle}` 198 ); 199 this.setAttribute("title", `${this.videoPlay}: ${this.videoTitle}`); 200 if (this.autoLoad) { 201 this.initIntersectionObserver(); 202 } 203 } 204 /** 205 * Lifecycle method that we use to listen for attribute changes to period 206 * @param {*} name 207 * @param {*} oldVal 208 * @param {*} newVal 209 */ 210 attributeChangedCallback(name, oldVal, newVal) { 211 switch (name) { 212 case "videoid": { 213 if (oldVal !== newVal) { 214 this.setupComponent(); 215 // if we have a previous iframe, remove it and the activated class 216 if (this.domRefFrame.classList.contains("lvo-activated")) { 217 this.domRefFrame.classList.remove("lvo-activated"); 218 this.shadowRoot.querySelector("iframe").remove(); 219 } 220 } 221 break; 222 } 223 default: 224 break; 225 } 226 } 227 /** 228 * Inject the iframe into the component body 229 */ 230 addIframe() { 231 if (!this.iframeLoaded) { 232 /** 233 * Vimeo example embed markup: 234 * 235 * <iframe src="https://player.vimeo.com/video/364402896#t=1m3s" 236 * width="640" height="360" 237 * frameborder="0" 238 * allow="autoplay; fullscreen" allowfullscreen> 239 * </iframe> 240 */ 241 // FIXME: add a setting for autoplay 242 let apValue = 243 (this.autoLoad && this.autoPlay) || !this.autoLoad 244 ? "autoplay=1" 245 : ""; 246 let srcUrl = new URL( 247 `/video/${this.videoId}?${apValue}&#t=${this.videoStartAt}`, 248 "https://player.vimeo.com/" 249 ); 250 // TODO: construct src value w/ URL constructor 251 const iframeHTML = ` 252<iframe frameborder="0" 253 allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" 254 allowfullscreen src="${srcUrl}"></iframe>`; 255 this.domRefFrame.insertAdjacentHTML("beforeend", iframeHTML); 256 this.domRefFrame.classList.add("lvo-activated"); 257 this.iframeLoaded = true; 258 } 259 } 260 /** 261 * Setup the placeholder image for the component 262 */ 263 async initImagePlaceholder() { 264 // TODO(slightlyoff): TODO: cache API responses 265 // we don't know which image type to preload, so warm the connection 266 LiteVimeoEmbed.addPrefetch("preconnect", "https://i.vimeocdn.com/"); 267 // API is the video-id based 268 // http://vimeo.com/api/v2/video/364402896.json 269 let api_url = `https://vimeo.com/api/v2/video/${this.videoId}.json`; 270 // Now fetch the JSON that locates our placeholder from vimeo's JSON API 271 let api_response = (await (await fetch(api_url)).json())[0]; 272 // Extract the image id, e.g. 819916979, from a URL like: 273 // thumbnail_large: "https://i.vimeocdn.com/video/819916979_640.jpg" 274 let tn_large = api_response.thumbnail_large; 275 let imgId = tn_large.substr(tn_large.lastIndexOf("/") + 1).split("_")[0]; 276 // const posterUrlWebp = `https://i.ytimg.com/vi_webp/${this.videoId}/hqdefault.webp`; 277 const posterUrlWebp = `https://i.vimeocdn.com/video/${imgId}.webp?mw=1280&mh=720&q=70`; 278 const posterUrlJpeg = `https://i.vimeocdn.com/video/${imgId}.jpg?mw=1280&mh=720&q=70`; 279 this.domRefImg.webp.srcset = posterUrlWebp; 280 this.domRefImg.jpeg.srcset = posterUrlJpeg; 281 this.domRefImg.fallback.src = posterUrlJpeg; 282 this.domRefImg.fallback.setAttribute( 283 "aria-label", 284 `${this.videoPlay}: ${this.videoTitle}` 285 ); 286 this.domRefImg.fallback.setAttribute( 287 "alt", 288 `${this.videoPlay}: ${this.videoTitle}` 289 ); 290 } 291 /** 292 * Setup the Intersection Observer to load the iframe when scrolled into view 293 */ 294 initIntersectionObserver() { 295 if ( 296 "IntersectionObserver" in window && 297 "IntersectionObserverEntry" in window 298 ) { 299 const options = { 300 root: null, 301 rootMargin: "0px", 302 threshold: 0 303 }; 304 const observer = new IntersectionObserver((entries, observer) => { 305 entries.forEach(entry => { 306 if (entry.isIntersecting && !this.iframeLoaded) { 307 LiteVimeoEmbed.warmConnections(); 308 this.addIframe(); 309 observer.unobserve(this); 310 } 311 }); 312 }, options); 313 observer.observe(this); 314 } 315 } 316 /** 317 * Add a <link rel={preload | preconnect} ...> to the head 318 * @param {*} kind 319 * @param {*} url 320 * @param {*} as 321 */ 322 static addPrefetch(kind, url, as) { 323 const linkElem = document.createElement("link"); 324 linkElem.rel = kind; 325 linkElem.href = url; 326 if (as) { 327 linkElem.as = as; 328 } 329 linkElem.crossOrigin = "true"; 330 document.head.append(linkElem); 331 } 332 /** 333 * Begin preconnecting to warm up the iframe load Since the embed's netwok 334 * requests load within its iframe, preload/prefetch'ing them outside the 335 * iframe will only cause double-downloads. So, the best we can do is warm up 336 * a few connections to origins that are in the critical path. 337 * 338 * Maybe `<link rel=preload as=document>` would work, but it's unsupported: 339 * http://crbug.com/593267 But TBH, I don't think it'll happen soon with Site 340 * Isolation and split caches adding serious complexity. 341 */ 342 static warmConnections() { 343 if (LiteVimeoEmbed.preconnected) return; 344 // Host that Vimeo uses to serve JS needed by player 345 LiteVimeoEmbed.addPrefetch("preconnect", "https://f.vimeocdn.com"); 346 // The iframe document comes from player.vimeo.com 347 LiteVimeoEmbed.addPrefetch("preconnect", "https://player.vimeo.com"); 348 // Image for placeholder comes from i.vimeocdn.com 349 LiteVimeoEmbed.addPrefetch("preconnect", "https://i.vimeocdn.com"); 350 LiteVimeoEmbed.preconnected = true; 351 } 352 } 353 LiteVimeoEmbed.preconnected = false;
354 return LiteVimeoEmbed; 355})(); 356// Register custom element 357customElements.define("lite-vimeo", LiteVimeoEmbed); 358//# sourceMappingURL=lite-vimeo.js.map
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.