1export class LiteYTEmbed extends HTMLElement { 2 constructor() { 3 super(); 4 this.isIframeLoaded = false; 5 this.setupDom(); 6 } 7 static get observedAttributes() { 8 return ['videoid', 'playlistid']; 9 } 10 connectedCallback() { 11 this.addEventListener('pointerover', LiteYTEmbed.warmConnections, { 12 once: true, 13 }); 14 this.addEventListener('click', () => this.addIframe()); 15 } 16 get videoId() { 17 return encodeURIComponent(this.getAttribute('videoid') || ''); 18 } 19 set videoId(id) { 20 this.setAttribute('videoid', id); 21 } 22 get playlistId() { 23 return encodeURIComponent(this.getAttribute('playlistid') || ''); 24 } 25 set playlistId(id) { 26 this.setAttribute('playlistid', id); 27 } 28 get videoTitle() { 29 return this.getAttribute('videotitle') || 'Video'; 30 } 31 set videoTitle(title) { 32 this.setAttribute('videotitle', title); 33 } 34 get videoPlay() { 35 return this.getAttribute('videoPlay') || 'Play'; 36 } 37 set videoPlay(name) { 38 this.setAttribute('videoPlay', name); 39 } 40 get videoStartAt() { 41 return Number(this.getAttribute('videoStartAt') || '0'); 42 } 43 set videoStartAt(time) { 44 this.setAttribute('videoStartAt', String(time)); 45 } 46 get autoLoad() { 47 return this.hasAttribute('autoload'); 48 } 49 get noCookie() { 50 return this.hasAttribute('nocookie'); 51 } 52 get posterQuality() { 53 return this.getAttribute('posterquality') || 'hqdefault'; 54 } 55 get posterLoading() { 56 return this.getAttribute('posterloading') || 'lazy'; 57 } 58 get params() { 59 return `start=${this.videoStartAt}&${this.getAttribute('params')}`; 60 } 61 setupDom() { 62 const shadowDom = this.attachShadow({ mode: 'open' }); 63 shadowDom.innerHTML = ` 64 <style> 65 :host { 66 contain: content; 67 display: block; 68 position: relative; 69 width: 100%; 70 padding-bottom: calc(100% / (16 / 9)); 71 --lyt-animation: all 0.2s cubic-bezier(0, 0, 0.2, 1); 72 --lyt-play-btn-default: #212121; 73 --lyt-play-btn-hover: #f00; 74 } 75 76 #frame, #fallbackPlaceholder, iframe { 77 position: absolute; 78 width: 100%; 79 height: 100%; 80 left: 0; 81 } 82 83 #frame { 84 cursor: pointer; 85 } 86 87 #fallbackPlaceholder { 88 object-fit: cover; 89 } 90 91 #frame::before { 92 content: ''; 93 display: block; 94 position: absolute; 95 top: 0; 96 background-image: linear-gradient(180deg, #111 -20%, transparent 90%); 97 height: 60px; 98 width: 100%; 99 transition: var(--lyt-animation); 100 z-index: 1; 101 } 102 103 #playButton { 104 width: 70px; 105 height: 46px; 106 background-color: var(--lyt-play-btn-hover); 107 z-index: 1; 108 opacity: 0.8; 109 border-radius: 14%; 110 transition: var(--lyt-animation); 111 border: 0; 112 } 113 114 #frame:hover > #playButton { 115 background-color: var(--lyt-play-btn-hover); 116 opacity: 1; 117 } 118 119 #playButton:before { 120 content: ''; 121 border-style: solid; 122 border-width: 11px 0 11px 19px; 123 border-color: transparent transparent transparent #fff; 124 } 125 126 #playButton, 127 #playButton:before { 128 position: absolute; 129 top: 50%; 130 left: 50%; 131 transform: translate3d(-50%, -50%, 0); 132 } 133 134 /* Post-click styles */ 135 .activated { 136 cursor: unset; 137 } 138 139 #frame.activated::before, 140 #frame.activated > #playButton { 141 display: none; 142 } 143 </style> 144 <div id="frame"> 145 <picture> 146 <source id="webpPlaceholder" type="image/webp"> 147 <source id="jpegPlaceholder" type="image/jpeg"> 148 <img id="fallbackPlaceholder" referrerpolicy="origin"> 149 </picture> 150 <button id="playButton"></button> 151 </div> 152 `; 153 this.domRefFrame = shadowDom.querySelector('#frame'); 154 this.domRefImg = { 155 fallback: shadowDom.querySelector('#fallbackPlaceholder'), 156 webp: shadowDom.querySelector('#webpPlaceholder'), 157 jpeg: shadowDom.querySelector('#jpegPlaceholder'), 158 }; 159 this.domRefPlayButton = shadowDom.querySelector('#playButton'); 160 } 161 setupComponent() { 162 this.initImagePlaceholder(); 163 this.domRefPlayButton.setAttribute('aria-label', `${this.videoPlay}: ${this.videoTitle}`); 164 this.setAttribute('title', `${this.videoPlay}: ${this.videoTitle}`); 165 if (this.autoLoad) { 166 this.initIntersectionObserver(); 167 } 168 } 169 attributeChangedCallback(name, oldVal, newVal) { 170 switch (name) { 171 case 'videoid': 172 case 'playlistid': { 173 if (oldVal !== newVal) { 174 this.setupComponent(); 175 if (this.domRefFrame.classList.contains('activated')) { 176 this.domRefFrame.classList.remove('activated'); 177 this.shadowRoot.querySelector('iframe').remove(); 178 this.isIframeLoaded = false;
179 } 180 } 181 break; 182 } 183 default: 184 break; 185 } 186 } 187 addIframe(isIntersectionObserver = false) { 188 if (!this.isIframeLoaded) { 189 const autoplay = isIntersectionObserver ? 0 : 1; 190 const wantsNoCookie = this.noCookie ? '-nocookie' : ''; 191 let embedTarget; 192 if (this.playlistId) { 193 embedTarget = `?listType=playlist&list=${this.playlistId}&`; 194 } 195 else { 196 embedTarget = `${this.videoId}?`; 197 } 198 const iframeHTML = ` 199<iframe frameborder="0" 200 allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen 201 src="https://www.youtube${wantsNoCookie}.com/embed/${embedTarget}autoplay=${autoplay}&${this.params}" 202></iframe>`; 203 this.domRefFrame.insertAdjacentHTML('beforeend', iframeHTML); 204 this.domRefFrame.classList.add('activated'); 205 this.isIframeLoaded = true; 206 this.dispatchEvent(new CustomEvent('liteYoutubeIframeLoaded', { 207 detail: { 208 videoId: this.videoId, 209 }, 210 bubbles: true, 211 cancelable: true, 212 })); 213 } 214 } 215 initImagePlaceholder() { 216 LiteYTEmbed.addPrefetch('preconnect', 'https://i.ytimg.com/'); 217 const posterUrlWebp = `https://i.ytimg.com/vi_webp/${this.videoId}/${this.posterQuality}.webp`; 218 const posterUrlJpeg = `https://i.ytimg.com/vi/${this.videoId}/${this.posterQuality}.jpg`; 219 this.domRefImg.fallback.loading = this.posterLoading; 220 this.domRefImg.webp.srcset = posterUrlWebp; 221 this.domRefImg.jpeg.srcset = posterUrlJpeg; 222 this.domRefImg.fallback.src = posterUrlJpeg; 223 this.domRefImg.fallback.setAttribute('aria-label', `${this.videoPlay}: ${this.videoTitle}`); 224 this.domRefImg?.fallback?.setAttribute('alt', `${this.videoPlay}: ${this.videoTitle}`); 225 } 226 initIntersectionObserver() { 227 const options = { 228 root: null, 229 rootMargin: '0px', 230 threshold: 0, 231 }; 232 const observer = new IntersectionObserver((entries, observer) => { 233 entries.forEach(entry => { 234 if (entry.isIntersecting && !this.isIframeLoaded) { 235 LiteYTEmbed.warmConnections(); 236 this.addIframe(true); 237 observer.unobserve(this); 238 } 239 }); 240 }, options); 241 observer.observe(this); 242 } 243 static addPrefetch(kind, url, as) { 244 const linkElem = document.createElement('link'); 245 linkElem.rel = kind; 246 linkElem.href = url; 247 if (as) { 248 linkElem.as = as; 249 } 250 linkElem.crossOrigin = 'true'; 251 document.head.append(linkElem); 252 } 253 static warmConnections() { 254 if (LiteYTEmbed.isPreconnected) 255 return; 256 LiteYTEmbed.addPrefetch('preconnect', 'https://s.ytimg.com'); 257 LiteYTEmbed.addPrefetch('preconnect', 'https://www.youtube.com'); 258 LiteYTEmbed.addPrefetch('preconnect', 'https://www.google.com'); 259 LiteYTEmbed.addPrefetch('preconnect', 'https://googleads.g.doubleclick.net'); 260 LiteYTEmbed.addPrefetch('preconnect', 'https://static.doubleclick.net'); 261 LiteYTEmbed.isPreconnected = true; 262 } 263} 264LiteYTEmbed.isPreconnected = false; 265customElements.define('lite-youtube', LiteYTEmbed); 266//# sourceMappingURL=lite-youtube.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.