https://cdn.clarku.edu/wp-content/plugins/clarku-component-library…/view.js?ver=5fc397d58c3801f32ef3
1/******/ (() => { // webpackBootstrap 2/******/ var __webpack_modules__ = ({ 3 4/***/ "./src/youtube-lite/lite-yt-embed.js" 5/*!*******************************************!*\ 6 !*** ./src/youtube-lite/lite-yt-embed.js ***! 7 \*******************************************/ 8() { 9 10/** 11 * A lightweight youtube embed. Still should feel the same to the user, just MUCH faster to initialize and paint. 12 * 13 * Thx to these as the inspiration 14 * https://storage.googleapis.com/amp-vs-non-amp/youtube-lazy.html 15 * https://autoplay-youtube-player.glitch.me/ 16 * 17 * Once built it, I also found these: 18 * https://github.com/ampproject/amphtml/blob/master/extensions/amp-youtube (ðð) 19 * https://github.com/Daugilas/lazyYT 20 * https://github.com/vb/lazyframe 21 */ 22class LiteYTEmbed extends HTMLElement { 23 constructor() { 24 super(); 25 this.currentContainer = null; 26 let ltYtClose = document.querySelectorAll('.modal button.close'); 27 ltYtClose.forEach(close => { 28 close.addEventListener('click', e => this.closeStopYTPlayer(e.target.parentElement), false); 29 }); 30 } 31 connectedCallback() { 32 this.videoId = this.getAttribute('videoid'); 33 let playBtnEl = this.querySelector('.lyt-playbtn,.lty-playbtn'); 34 // A label for the button takes priority over a [playlabel] attribute on the custom-element 35 this.playLabel = playBtnEl && playBtnEl.textContent.trim() || this.getAttribute('playlabel') || 'Play'; 36 this.dataset.title = this.getAttribute('title') || ""; 37 38 /** 39 * Lo, the youtube poster image! (aka the thumbnail, image placeholder, etc) 40 * 41 * See https://github.com/paulirish/lite-youtube-embed/blob/master/youtube-thumbnail-urls.md 42 */ 43 if (!this.style.backgroundImage) { 44 this.style.backgroundImage = `url("https://i.ytimg.com/vi/${this.videoId}/hqdefault.jpg")`; 45 this.upgradePosterImage(); 46 } 47 48 // Set up play button, and its visually hidden label 49 if (!playBtnEl) { 50 playBtnEl = document.createElement('button'); 51 playBtnEl.type = 'button'; 52 // Include the mispelled 'lty-' in case it's still being used. https://github.com/paulirish/lite-youtube-embed/issues/65 53 playBtnEl.classList.add('lyt-playbtn', 'lty-playbtn'); 54 this.append(playBtnEl); 55 } 56 if (!playBtnEl.textContent) { 57 const playBtnLabelEl = document.createElement('span'); 58 playBtnLabelEl.className = 'lyt-visually-hidden'; 59 playBtnLabelEl.textContent = this.playLabel; 60 playBtnEl.append(playBtnLabelEl); 61 } 62 this.addNoscriptIframe(); 63 64 // for the PE pattern, change anchor's semantics to button 65 if (playBtnEl.nodeName === 'A') { 66 playBtnEl.removeAttribute('href'); 67 playBtnEl.setAttribute('tabindex', '0'); 68 playBtnEl.setAttribute('role', 'button'); 69 // fake button needs keyboard help 70 playBtnEl.addEventListener('keydown', e => { 71 if (e.key === 'Enter' || e.key === ' ') { 72 e.preventDefault(); 73 this.activate(); 74 } 75 }); 76 } 77 78 // On hover (or tap), warm up the TCP connections we're (likely) about to use. 79 this.addEventListener('pointerover', LiteYTEmbed.warmConnections, { 80 once: true 81 }); 82 this.addEventListener('focusin', LiteYTEmbed.warmConnections, { 83 once: true 84 }); 85 86 // Once the user clicks, add the real iframe and drop our play button 87 // TODO: In the future we could be like amp-youtube and silently swap in the iframe during idle time 88 // We'd want to only do this for in-viewport or near-viewport ones: https://github.com/ampproject/amphtml/pull/5003 89 this.addEventListener('click', this.activate); 90 91 // Chrome & Edge desktop have no problem with the basic YouTube Embed with ?autoplay=1 92 // However Safari desktop and most/all mobile browsers do not successfully track the user gesture of clicking through the creation/loading of the iframe, 93 // so they don't autoplay automatically. Instead we must load an additional 2 sequential JS files (1KB + 165KB) (un-br) for the YT Player API 94 // TODO: Try loading the the YT API in parallel with our iframe and then attaching/playing it. #82 95 this.needsYTApi = this.hasAttribute("js-api") || navigator.vendor.includes('Apple') || navigator.userAgent.includes('Mobi'); 96 } 97 98 /** 99 * Add a <link rel={preload | preconnect} ...> to the head 100 */ 101 static addPrefetch(kind, url, as) { 102 const linkEl = document.createElement('link'); 103 linkEl.rel = kind; 104 linkEl.href = url; 105 if (as) { 106 linkEl.as = as; 107 } 108 document.head.append(linkEl); 109 } 110 111 /** 112 * Begin pre-connecting to warm up the iframe load 113 * Since the embed's network requests load within its iframe, 114 * preload/prefetch'ing them outside the iframe will only cause double-downloads. 115 * So, the best we can do is warm up a few connections to origins that are in the critical path. 116 * 117 * Maybe `<link rel=preload as=document>` would work, but it's unsupported: http://crbug.com/593267 118 * But TBH, I don't think it'll happen soon with Site Isolation and split caches adding serious complexity. 119 */ 120 static warmConnections() { 121 if (LiteYTEmbed.preconnected) return; 122 123 // The iframe document and most of its subresources come right off youtube.com 124 LiteYTEmbed.addPrefetch('preconnect', 'https://www.youtube-nocookie.com'); 125 // The botguard script is fetched off from google.com 126 LiteYTEmbed.addPrefetch('preconnect', 'https://www.google.com'); 127 128 // Not certain if these ad related domains are in the critical path. Could verify with domain-specific throttling. 129 LiteYTEmbed.addPrefetch('preconnect', 'https://googleads.g.doubleclick.net'); 130 LiteYTEmbed.addPrefetch('preconnect', 'https://static.doubleclick.net'); 131 LiteYTEmbed.preconnected = true; 132 } 133 fetchYTPlayerApi() { 134 if (window.YT || window.YT && window.YT.Player) return; 135 this.ytApiPromise = new Promise((res, rej) => { 136 var el = document.createElement('script'); 137 el.src = 'https://www.youtube.com/iframe_api'; 138 el.async = true; 139 el.onload = _ => { 140 YT.ready(res); 141 }; 142 el.onerror = rej; 143 this.append(el); 144 }); 145 } 146 147 /** Return the YT Player API instance. (Public L-YT-E API) */ 148 async getYTPlayer() { 149 if (!this.playerPromise) { 150 await this.activate(); 151 } 152 return this.playerPromise; 153 } 154 async addYTPlayerIframe() { 155 this.fetchYTPlayerApi(); 156 await this.ytApiPromise; 157 const videoPlaceholderEl = document.createElement('div'); 158 this.append(videoPlaceholderEl); 159 const paramsObj = Object.fromEntries(this.getParams().entries()); 160 this.playerPromise = new Promise(resolve => { 161 let player = new YT.Player(videoPlaceholderEl, { 162 width: '100%', 163 videoId: this.videoId, 164 playerVars: paramsObj, 165 events: { 166 'onReady': event => { 167 event.target.playVideo(); 168 resolve(player); 169 } 170 } 171 }); 172 }); 173 } 174 175 // Add the iframe within <noscript> for indexability discoverability. See https://github.com/paulirish/lite-youtube-embed/issues/105 176 addNoscriptIframe() { 177 const iframeEl = this.createBasicIframe(); 178 const noscriptEl = document.createElement('noscript'); 179 // Appending into noscript isn't equivalant for mysterious reasons: https://html.spec.whatwg.org/multipage/scripting.html#the-noscript-element 180 noscriptEl.innerHTML = iframeEl.outerHTML; 181 this.append(noscriptEl); 182 } 183 getParams() { 184 const params = new URLSearchParams(this.getAttribute('params') || []); 185 params.append('autoplay', '1'); 186 params.append('playsinline', '1'); 187 return params; 188 } 189 async activate() { 190 if (this.classList.contains('lyt-activated')) return; 191 if (this.classList.contains('lyt-closing')) return; 192 let modal = this.parentElement; 193 if (modal.classList.contains('modal')) { 194 this.currentContainer = this.parentElement.parentElement; //set current container to reference later, part of safari 16.5 fix 195 let p = modal.style; 196 document.documentElement.style.overflow = 'hidden'; //Prevent Scrolling so return animation doesn't break 197 p.width = this.parentElement.getBoundingClientRect().width + 'px'; // 198 p.height = this.parentElement.getBoundingClientRect().height + 'px'; // Calculate Video Origin for animation starting spot 199 p.top = this.parentElement.getBoundingClientRect().top + 'px'; // 200 p.left = this.parentElement.getBoundingClientRect().left + 'px'; // 201 modal.parentElement.style.height = modal.parentElement.getBoundingClientRect().height + 'px'; 202 p.position = 'fixed'; 203 document.documentElement.append(this.parentElement); //move out of body for safari 16.5 fix 204 await new Promise(r => setTimeout(r, 0)); //Forces browser to wait for previous function to finish 205 this.classList.add('lyt-activated'); 206 await new Promise(r => setTimeout(r, 150)); // Wait for animation before adding iframe, prevents glitchy animation 207 modal.addEventListener('click', e => { 208 this.closeStopYTPlayer(); 209 }, { 210 once: true 211 }); 212 } else { 213 this.classList.add('lyt-activated'); 214 } 215 if (this.needsYTApi) { 216 return this.addYTPlayerIframe(this.getParams()); 217 } 218 const iframeEl = this.createBasicIframe(); 219 this.append(iframeEl); 220 221 // Set focus for a11y
222 iframeEl.focus(); 223 } 224 async closeStopYTPlayer() { 225 let el = this; 226 let player = await this.getYTPlayer(); 227 player.stopVideo(); 228 el.classList.add('lyt-closing'); 229 el.classList.remove('lyt-activated'); 230 try { 231 el.removeChild(el.querySelector('iframe')); 232 } catch (e) {} //prevent errors from user double clicking showing in console 233 await new Promise(r => setTimeout(r, 150)); //throttle activation for 1 second for animation 234 el.currentContainer.append(el.parentElement); //safari 16.5 fix 235 el.parentElement.style.removeProperty('position'); 236 el.classList.remove('lyt-closing'); 237 ['height', 'width', 'top', 'left'].forEach(prop => el.parentElement.style.removeProperty(prop)); 238 el.parentElement.parentElement.style.removeProperty('height'); 239 document.documentElement.style.removeProperty('overflow'); 240 } 241 createBasicIframe() { 242 const iframeEl = document.createElement('iframe'); 243 iframeEl.width = 560; 244 iframeEl.height = 315; 245 // No encoding necessary as [title] is safe. https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#:~:text=Safe%20HTML%20Attributes%20include 246 iframeEl.title = this.playLabel; 247 iframeEl.allow = 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture'; 248 iframeEl.allowFullscreen = true; 249 // AFAIK, the encoding here isn't necessary for XSS, but we'll do it only because this is a URL 250 // https://stackoverflow.com/q/64959723/89484 251 iframeEl.src = `https://www.youtube-nocookie.com/embed/${encodeURIComponent(this.videoId)}?${this.getParams().toString()}`; 252 return iframeEl; 253 } 254 255 /** 256 * In the spirit of the `lowsrc` attribute and progressive JPEGs, we'll upgrade the reliable 257 * poster image to a higher resolution one, if it's available. 258 * Interestingly this sddefault webp is often smaller in filesize, but we will still attempt it second 259 * because getting _an_ image in front of the user if our first priority. 260 * 261 * See https://github.com/paulirish/lite-youtube-embed/blob/master/youtube-thumbnail-urls.md for more details 262 */ 263 upgradePosterImage() { 264 // Defer to reduce network contention. 265 setTimeout(() => { 266 const webpUrl = `https://i.ytimg.com/vi_webp/${this.videoId}/sddefault.webp`; 267 const img = new Image(); 268 img.fetchPriority = 'low'; // low priority to reduce network contention 269 img.referrerpolicy = 'origin'; // Not 100% sure it's needed, but https://github.com/ampproject/amphtml/pull/3940 270 img.src = webpUrl; 271 img.onload = e => { 272 // A pretty ugly hack since onerror won't fire on YouTube image 404. This is (probably) due to 273 // Youtube's style of returning data even with a 404 status. That data is a 120x90 placeholder image. 274 // ⦠per "annoying yt 404 behavior" in the .md 275 const noAvailablePoster = e.target.naturalHeight == 90 && e.target.naturalWidth == 120; 276 if (noAvailablePoster) return; 277 this.style.backgroundImage = `url("${webpUrl}")`; 278 }; 279 }, 100); 280 } 281} 282// Register custom element 283customElements.define('lite-youtube', LiteYTEmbed); 284 285/***/ } 286 287/******/ }); 288/************************************************************************/ 289/******/ // The module cache 290/******/ var __webpack_module_cache__ = {}; 291/******/ 292/******/ // The require function 293/******/ function __webpack_require__(moduleId) { 294/******/ // Check if module is in cache 295/******/ var cachedModule = __webpack_module_cache__[moduleId]; 296/******/ if (cachedModule !== undefined) { 297/******/ return cachedModule.exports; 298/******/ } 299/******/ // Check if module exists (development only) 300/******/ if (__webpack_modules__[moduleId] === undefined) { 301/******/ var e = new Error("Cannot find module '" + moduleId + "'"); 302/******/ e.code = 'MODULE_NOT_FOUND'; 303/******/ throw e; 304/******/ } 305/******/ // Create a new module (and put it into the cache) 306/******/ var module = __webpack_module_cache__[moduleId] = { 307/******/ // no module.id needed 308/******/ // no module.loaded needed 309/******/ exports: {} 310/******/ }; 311/******/ 312/******/ // Execute the module function 313/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); 314/******/ 315/******/ // Return the exports of the module 316/******/ return module.exports; 317/******/ } 318/******/ 319/************************************************************************/ 320/******/ /* webpack/runtime/compat get default export */ 321/******/ (() => { 322/******/ // getDefaultExport function for compatibility with non-harmony modules 323/******/ __webpack_require__.n = (module) => {
vendor: 2,193 bytes, lines 324-377
324/******/ var getter = module && module.__esModule ? 325/******/ () => (module['default']) : 326/******/ () => (module); 327/******/ __webpack_require__.d(getter, { a: getter }); 328/******/ return getter; 329/******/ }; 330/******/ })(); 331/******/ 332/******/ /* webpack/runtime/define property getters */ 333/******/ (() => { 334/******/ // define getter functions for harmony exports 335/******/ __webpack_require__.d = (exports, definition) => { 336/******/ for(var key in definition) { 337/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { 338/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); 339/******/ } 340/******/ } 341/******/ }; 342/******/ })(); 343/******/ 344/******/ /* webpack/runtime/hasOwnProperty shorthand */ 345/******/ (() => { 346/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) 347/******/ })(); 348/******/ 349/******/ /* webpack/runtime/make namespace object */ 350/******/ (() => { 351/******/ // define __esModule on exports 352/******/ __webpack_require__.r = (exports) => { 353/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 354/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 355/******/ } 356/******/ Object.defineProperty(exports, '__esModule', { value: true }); 357/******/ }; 358/******/ })(); 359/******/ 360/************************************************************************/ 361var __webpack_exports__ = {}; 362// This entry needs to be wrapped in an IIFE because it needs to be in strict mode. 363(() => { 364"use strict"; 365/*!**********************************!*\ 366 !*** ./src/youtube-lite/view.js ***! 367 \**********************************/ 368__webpack_require__.r(__webpack_exports__); 369/* harmony import */ var _lite_yt_embed_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./lite-yt-embed.js */ "./src/youtube-lite/lite-yt-embed.js"); 370/* harmony import */ var _lite_yt_embed_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_lite_yt_embed_js__WEBPACK_IMPORTED_MODULE_0__); 371 372//import './clark-yt-embed.js' 373})(); 374 375/******/ })() 376; 377//# sourceMappingURL=view.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.