vendor: 12,960 bytes, lines 1-340
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 } 77 else { 78 this.removeAttribute('autoload'); 79 } 80 } 81 get autoPlay() { 82 return this.hasAttribute('autoplay'); 83 } 84 set autoPlay(value) { 85 if (value) { 86 this.setAttribute('autoplay', 'autoplay'); 87 } 88 else { 89 this.removeAttribute('autoplay'); 90 } 91 } 92 /** 93 * Define our shadowDOM for the component 94 */ 95 setupDom() { 96 const shadowDom = this.attachShadow({ mode: 'open' }); 97 shadowDom.innerHTML = ` 98 <style> 99 :host { 100 contain: content; 101 display: block; 102 position: relative; 103 width: 100%; 104 padding-bottom: calc(100% / (16 / 9)); 105 } 106 107 #frame, #fallbackPlaceholder, iframe { 108 position: absolute; 109 width: 100%; 110 height: 100%; 111 } 112 113 #frame { 114 cursor: pointer; 115 } 116 117 #fallbackPlaceholder { 118 object-fit: cover; 119 } 120 121 #frame::before { 122 content: ''; 123 display: block; 124 position: absolute; 125 top: 0; 126 background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==); 127 background-position: top; 128 background-repeat: repeat-x; 129 height: 60px; 130 padding-bottom: 50px; 131 width: 100%; 132 transition: all 0.2s cubic-bezier(0, 0, 0.2, 1); 133 z-index: 1; 134 } 135 /* play button */ 136 .lvo-playbtn { 137 width: 70px; 138 height: 46px; 139 background-color: #212121; 140 z-index: 1; 141 opacity: 0.8; 142 border-radius: 10%; 143 transition: all 0.2s cubic-bezier(0, 0, 0.2, 1); 144 border: 0; 145 } 146 #frame:hover .lvo-playbtn { 147 background-color: rgb(98, 175, 237); 148 opacity: 1; 149 } 150 /* play button triangle */ 151 .lvo-playbtn:before { 152 content: ''; 153 border-style: solid; 154 border-width: 11px 0 11px 19px; 155 border-color: transparent transparent transparent #fff; 156 } 157 .lvo-playbtn, 158 .lvo-playbtn:before { 159 position: absolute; 160 top: 50%; 161 left: 50%; 162 transform: translate3d(-50%, -50%, 0); 163 } 164 165 /* Post-click styles */ 166 .lvo-activated { 167 cursor: unset; 168 } 169 170 #frame.lvo-activated::before, 171 .lvo-activated .lvo-playbtn { 172 display: none; 173 } 174 </style> 175 <div id="frame"> 176 <picture> 177 <source id="webpPlaceholder" type="image/webp"> 178 <source id="jpegPlaceholder" type="image/jpeg"> 179 <img id="fallbackPlaceholder" referrerpolicy="origin"> 180 </picture> 181 <button class="lvo-playbtn"></button> 182 </div> 183 `; 184 this.domRefFrame = this.shadowRoot.querySelector('#frame'); 185 this.domRefImg = { 186 fallback: this.shadowRoot.querySelector('#fallbackPlaceholder'), 187 webp: this.shadowRoot.querySelector('#webpPlaceholder'), 188 jpeg: this.shadowRoot.querySelector('#jpegPlaceholder'), 189 }; 190 this.domRefPlayButton = this.shadowRoot.querySelector('.lvo-playbtn'); 191 } 192 /** 193 * Parse our attributes and fire up some placeholders 194 */ 195 setupComponent() { 196 this.initImagePlaceholder(); 197 this.domRefPlayButton.setAttribute('aria-label', `${this.videoPlay}: ${this.videoTitle}`); 198 this.setAttribute('title', `${this.videoPlay}: ${this.videoTitle}`); 199 if (this.autoLoad) { 200 this.initIntersectionObserver(); 201 } 202 } 203 /** 204 * Lifecycle method that we use to listen for attribute changes to period 205 * @param {*} name 206 * @param {*} oldVal 207 * @param {*} newVal 208 */ 209 attributeChangedCallback(name, oldVal, newVal) { 210 switch (name) { 211 case 'videoid': { 212 if (oldVal !== newVal) { 213 this.setupComponent(); 214 // if we have a previous iframe, remove it and the activated class 215 if (this.domRefFrame.classList.contains('lvo-activated')) { 216 this.domRefFrame.classList.remove('lvo-activated'); 217 this.shadowRoot.querySelector('iframe').remove(); 218 } 219 } 220 break; 221 } 222 default: 223 break; 224 } 225 } 226 /** 227 * Inject the iframe into the component body 228 */ 229 addIframe() { 230 if (!this.iframeLoaded) { 231 /** 232 * Vimeo example embed markup: 233 * 234 * <iframe src="https://player.vimeo.com/video/364402896#t=1m3s" 235 * width="640" height="360" 236 * frameborder="0" 237 * allow="autoplay; fullscreen" allowfullscreen> 238 * </iframe> 239 */ 240 // FIXME: add a setting for autoplay 241 let apValue = ((this.autoLoad && this.autoPlay) || (!this.autoLoad)) ? 242 "autoplay=1" : ""; 243 let srcUrl = new URL(`/video/${this.videoId}?${apValue}&#t=${this.videoStartAt}`, "https://player.vimeo.com/"); 244 // TODO: construct src value w/ URL constructor 245 const iframeHTML = ` 246<iframe frameborder="0" 247 allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" 248 allowfullscreen src="${srcUrl}"></iframe>`; 249 this.domRefFrame.insertAdjacentHTML('beforeend', iframeHTML); 250 this.domRefFrame.classList.add('lvo-activated'); 251 this.iframeLoaded = true; 252 } 253 } 254 /** 255 * Setup the placeholder image for the component 256 */ 257 async initImagePlaceholder() { 258 // TODO(slightlyoff): TODO: cache API responses 259 // we don't know which image type to preload, so warm the connection 260 LiteVimeoEmbed.addPrefetch('preconnect', 'https://i.vimeocdn.com/'); 261 // API is the video-id based 262 // http://vimeo.com/api/v2/video/364402896.json 263 let api_url = `https://vimeo.com/api/v2/video/${this.videoId}.json`; 264 // Now fetch the JSON that locates our placeholder from vimeo's JSON API 265 let api_response = (await (await fetch(api_url)).json())[0]; 266 // Extract the image id, e.g. 819916979, from a URL like: 267 // thumbnail_large: "https://i.vimeocdn.com/video/819916979_640.jpg" 268 let tn_large = api_response.thumbnail_large; 269 let imgId = (tn_large.substr(tn_large.lastIndexOf("/") + 1)).split("_")[0]; 270 // const posterUrlWebp = `https://i.ytimg.com/vi_webp/${this.videoId}/hqdefault.webp`; 271 const posterUrlWebp = `https://i.vimeocdn.com/video/${imgId}.webp?mw=1100&mh=619&q=70`; 272 const posterUrlJpeg = `https://i.vimeocdn.com/video/${imgId}.jpg?mw=1100&mh=619&q=70`; 273 this.domRefImg.webp.srcset = posterUrlWebp; 274 this.domRefImg.jpeg.srcset = posterUrlJpeg; 275 this.domRefImg.fallback.src = posterUrlJpeg; 276 this.domRefImg.fallback.setAttribute('aria-label', `${this.videoPlay}: ${this.videoTitle}`); 277 this.domRefImg.fallback.setAttribute('alt', `${this.videoPlay}: ${this.videoTitle}`); 278 } 279 /** 280 * Setup the Intersection Observer to load the iframe when scrolled into view 281 */ 282 initIntersectionObserver() { 283 if ('IntersectionObserver' in window && 284 'IntersectionObserverEntry' in window) { 285 const options = { 286 root: null, 287 rootMargin: '0px', 288 threshold: 0, 289 }; 290 const observer = new IntersectionObserver((entries, observer) => { 291 entries.forEach(entry => { 292 if (entry.isIntersecting && !this.iframeLoaded) { 293 LiteVimeoEmbed.warmConnections(); 294 this.addIframe(); 295 observer.unobserve(this); 296 } 297 }); 298 }, options); 299 observer.observe(this); 300 } 301 } 302 /** 303 * Add a <link rel={preload | preconnect} ...> to the head 304 * @param {*} kind 305 * @param {*} url 306 * @param {*} as 307 */ 308 static addPrefetch(kind, url, as) { 309 const linkElem = document.createElement('link'); 310 linkElem.rel = kind; 311 linkElem.href = url; 312 if (as) { 313 linkElem.as = as; 314 } 315 linkElem.crossOrigin = 'true'; 316 document.head.append(linkElem); 317 } 318 /** 319 * Begin preconnecting to warm up the iframe load Since the embed's netwok 320 * requests load within its iframe, preload/prefetch'ing them outside the 321 * iframe will only cause double-downloads. So, the best we can do is warm up 322 * a few connections to origins that are in the critical path. 323 * 324 * Maybe `<link rel=preload as=document>` would work, but it's unsupported: 325 * http://crbug.com/593267 But TBH, I don't think it'll happen soon with Site 326 * Isolation and split caches adding serious complexity. 327 */ 328 static warmConnections() { 329 if (LiteVimeoEmbed.preconnected) 330 return; 331 // Host that Vimeo uses to serve JS needed by player 332 LiteVimeoEmbed.addPrefetch('preconnect', 'https://f.vimeocdn.com'); 333 // The iframe document comes from player.vimeo.com 334 LiteVimeoEmbed.addPrefetch('preconnect', 'https://player.vimeo.com'); 335 // Image for placeholder comes from i.vimeocdn.com 336 LiteVimeoEmbed.addPrefetch('preconnect', 'https://i.vimeocdn.com'); 337 LiteVimeoEmbed.preconnected = true; 338 } 339 } 340 LiteVimeoEmbed.preconnected = false;
341 return LiteVimeoEmbed; 342})(); 343export { LiteVimeoEmbed }; 344// Register custom element 345customElements.define('lite-vimeo', LiteVimeoEmbed); 346//# sourceMappingURL=lite-vimeo.js.map 347/** 348 * A lightweight youtube embed. Still should feel the same to the user, just MUCH faster to initialize and paint. 349 * 350 * Thx to these as the inspiration 351 * https://storage.googleapis.com/amp-vs-non-amp/youtube-lazy.html 352 * https://autoplay-youtube-player.glitch.me/ 353 * 354 * Once built it, I also found these: 355 * https://github.com/ampproject/amphtml/blob/master/extensions/amp-youtube (ðð) 356 * https://github.com/Daugilas/lazyYT 357 * https://github.com/vb/lazyframe 358 */ 359class LiteYTEmbed extends HTMLElement { 360 connectedCallback() { 361 this.videoId = this.getAttribute('videoid'); 362 363 let playBtnEl = this.querySelector('.lty-playbtn'); 364 // A label for the button takes priority over a [playlabel] attribute on the custom-element 365 this.playLabel = (playBtnEl && playBtnEl.textContent.trim()) || this.getAttribute('playlabel') || 'Play'; 366 367 /** 368 * Lo, the youtube placeholder image! (aka the thumbnail, poster image, etc) 369 * 370 * See https://github.com/paulirish/lite-youtube-embed/blob/master/youtube-thumbnail-urls.md 371 * 372 */ 373 this.posterUrl = `https://i.ytimg.com/vi/${this.videoId}/hqdefault.jpg`; 374 // Warm the connection for the poster image 375 LiteYTEmbed.addPrefetch('preload', this.posterUrl, 'image'); 376 377 this.style.backgroundImage = `url("${this.posterUrl}")`; 378 379 // Set up play button, and its visually hidden label 380 if (!playBtnEl) { 381 playBtnEl = document.createElement('button'); 382 playBtnEl.type = 'button'; 383 playBtnEl.classList.add('lty-playbtn'); 384 this.append(playBtnEl); 385 } 386 if (!playBtnEl.textContent) { 387 const playBtnLabelEl = document.createElement('span'); 388 playBtnLabelEl.className = 'lyt-visually-hidden'; 389 playBtnLabelEl.textContent = this.playLabel; 390 playBtnEl.append(playBtnLabelEl); 391 } 392 393 // On hover (or tap), warm up the TCP connections we're (likely) about to use. 394 this.addEventListener('pointerover', LiteYTEmbed.warmConnections, {once: true}); 395 396 // Once the user clicks, add the real iframe and drop our play button 397 // TODO: In the future we could be like amp-youtube and silently swap in the iframe during idle time 398 // We'd want to only do this for in-viewport or near-viewport ones: https://github.com/ampproject/amphtml/pull/5003 399 this.addEventListener('click', e => this.addIframe()); 400 } 401 402 // // TODO: Support the the user changing the [videoid] attribute 403 // attributeChangedCallback() { 404 // } 405 406 /** 407 * Add a <link rel={preload | preconnect} ...> to the head 408 */ 409 static addPrefetch(kind, url, as) { 410 const linkEl = document.createElement('link'); 411 linkEl.rel = kind; 412 linkEl.href = url; 413 if (as) { 414 linkEl.as = as; 415 } 416 document.head.append(linkEl); 417 } 418 419 /** 420 * Begin pre-connecting to warm up the iframe load 421 * Since the embed's network requests load within its iframe, 422 * preload/prefetch'ing them outside the iframe will only cause double-downloads. 423 * So, the best we can do is warm up a few connections to origins that are in the critical path. 424 * 425 * Maybe `<link rel=preload as=document>` would work, but it's unsupported: http://crbug.com/593267 426 * But TBH, I don't think it'll happen soon with Site Isolation and split caches adding serious complexity. 427 */ 428 static warmConnections() { 429 if (LiteYTEmbed.preconnected) return; 430 431 // The iframe document and most of its subresources come right off youtube.com 432 LiteYTEmbed.addPrefetch('preconnect', 'https://www.youtube-nocookie.com'); 433 // The botguard script is fetched off from google.com 434 LiteYTEmbed.addPrefetch('preconnect', 'https://www.google.com'); 435 436 // Not certain if these ad related domains are in the critical path. Could verify with domain-specific throttling. 437 LiteYTEmbed.addPrefetch('preconnect', 'https://googleads.g.doubleclick.net'); 438 LiteYTEmbed.addPrefetch('preconnect', 'https://static.doubleclick.net'); 439 440 LiteYTEmbed.preconnected = true; 441 } 442 443 addIframe() { 444 const params = new URLSearchParams(this.getAttribute('params') || []); 445 params.append('autoplay', '1'); 446 447 const iframeEl = document.createElement('iframe'); 448 iframeEl.width = 560; 449 iframeEl.height = 315; 450 // No encoding necessary as [title] is safe. https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#:~:text=Safe%20HTML%20Attributes%20include 451 iframeEl.title = this.playLabel; 452 iframeEl.allow = 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture'; 453 iframeEl.allowFullscreen = true; 454 // AFAIK, the encoding here isn't necessary for XSS, but we'll do it only because this is a URL 455 // https://stackoverflow.com/q/64959723/89484 456 iframeEl.src = `https://www.youtube-nocookie.com/embed/${encodeURIComponent(this.videoId)}?${params.toString()}`; 457 this.append(iframeEl); 458 459 this.classList.add('lyt-activated'); 460 461 // Set focus for a11y 462 this.querySelector('iframe').focus(); 463 } 464} 465// Register custome element 466customElements.define('lite-youtube', LiteYTEmbed); 467 468// import '../vendor/lite-vimeo-embed'; //Youtube embed - https://github.com/paulirish/lite-youtube-embed 469// import '../vendor/lite-youtube-embed'; //Vimeo embed - https://github.com/slightlyoff/lite-vimeo 470 471// var wpDebugBar;!function(e){var i;wpDebugBar=i={body:void 0,init:function(){i.body=e(document.body),i.toggle.init(
471),i.tabs(),i.actions.init()},isVisible:function(){return i.body.hasClass("debug-bar-visible")},toggle:{init:function(){e("#wp-admin-bar-debug-bar").click(function(t){if(t.preventDefault(),t.target.hash){var s=e(t.target.rel);if(i.isVisible()){if(s.hasClass("current"))return s.removeClass("current"),void i.toggle.visibility()}else i.toggle.visibility();e(".debug-menu-target").hide().trigger("debug-bar-hide"),e(".debug-menu-link").removeClass("current"),s.addClass("current"),e(t.target.hash).show().trigger("debug-bar-show")}else i.toggle.visibility()})},visibility:function(t){t=void 0===t?!i.isVisible():t,i.body.toggleClass("debug-bar-visible",t),e(this).toggleClass("active",t)}},tabs:function(){var i=e(".debug-menu-link"),t=e(".debug-menu-target");i.click(function(s){var r=e(this);s.preventDefault(),r.hasClass("current")||(t.hide().trigger("debug-bar-hide"),i.removeClass("current"),r.addClass("current"),e("#"+this.href.substr(this.href.indexOf("#")+1)).show().trigger("debug-bar-show"))})},actions:{init:function(){var t=e("#debug-bar-actions");e(document).keydown(function(e){var t=e.key||e.which||e.keyCode;27===t&&i.isVisible()&&(e.preventDefault(),i.actions.close())}),e(".maximize",t).click(i.actions.maximize),e(".restore",t).click(i.actions.restore),e(".close",t).click(i.actions.close)},maximize:function(){i.body.removeClass("debug-bar-partial"),i.body.addClass("debug-bar-maximized")},restore:function(){i.body.removeClass("debug-bar-maximized"),i.body.addClass("debug-bar-partial")},close:function(){i.toggle.visibility(!1)}}},e(wpDebugBar.init)}(jQuery);
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.