PageSourceSearch

https://direktno.hr/media/lite-youtube.js

js direktno.hr collected 2026-10-02 04:06:35 UTC 8,845 bytes, 266 lines download raw bytes

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.