PageSourceSearch

https://placidbay.com/wp-content/plugins/breakdance/subplugins/bre…/lite-vimeo.js?bd_ver=2.8.3

js placidbay.com collected 2026-10-07 00:04:19 UTC 11,885 bytes, 358 lines download raw bytes

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.