1(function (global, factory) { 2 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 3 typeof define === 'function' && define.amd ? define(factory) : 4 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.lazyframe = factory()); 5})(this, (function () { 'use strict'; 6 7 const Lazyframe = () => { 8 let settings; 9 const elements = []; 10 const defaults = { 11 vendor: undefined, 12 id: undefined, 13 src: undefined, 14 thumbnail: undefined, 15 title: undefined, 16 initialized: false, 17 y: undefined, 18 debounce: 250, 19 lazyload: true, 20 autoplay: true, 21 initinview: false, 22 onLoad: l => {}, 23 onAppend: l => {}, 24 onThumbnailLoad: img => {} 25 }; 26 const constants = { 27 regex: { 28 // youtube_nocookie: /(?:youtube-nocookie\.com\/\S*(?:(?:\/e(?:mbed))?\/|watch\?(?:\S*?&?v\=)))([a-zA-Z0-9_-]{6,11})/, 29 youtube: /(?:youtube\.com\/\S*(?:(?:\/e(?:mbed))?\/|watch\?(?:\S*?&?v\=))|youtu\.be\/)([a-zA-Z0-9_-]{6,11})/ 30 // vimeo: /vimeo\.com\/(?:video\/)?([0-9]*)(?:\?|)/, 31 }, 32 condition: { 33 youtube: m => m && m[1].length == 11 ? m[1] : false 34 // youtube_nocookie: (m) => (m && m[1].length == 11 ? m[1] : false), 35 // vimeo: (m) => 36 // (m && m[1].length === 9) || m[1].length === 8 ? m[1] : false, 37 }, 38 /** 39 * If we extend this solution to other providers. 40 * Then we can use https://noembed.com/embed?url= 41 * 42 * Note 43 * noembed currently fails for youtube playlist 44 * https://noembed.com/embed?url=https://www.youtube.com/embed/?list=PLRI8EpJLMtxZjAPSULWjeMvc2ywqgLhzC 45 * 46 * Since, it needs the video id of the first video to work properly 47 * https://noembed.com/embed?url=https://www.youtube.com/embed/9EvthWaXinM?list=PLRI8EpJLMtxZjAPSULWjeMvc2ywqgLhzC 48 */ 49 endpoint: src => `https://www.youtube.com/oembed?format=json&url=${encodeURIComponent(src)}`, 50 response: { 51 title: r => r.title, 52 thumbnail: r => r.thumbnail_url 53 } 54 }; 55 function init(elements, ...args) { 56 settings = Object.assign({}, defaults, args[0]); 57 if (typeof elements === 'string') { 58 const selector = document.querySelectorAll(elements); 59 for (let i = 0; i < selector.length; i++) { 60 loop(selector[i]); 61 } 62 } else if (typeof elements.length === 'undefined') { 63 loop(elements); 64 } else if (elements.length > 1) { 65 for (let i = 0; i < elements.length; i++) { 66 loop(elements[i]); 67 } 68 } else { 69 loop(elements[0]); 70 } 71 if (settings.lazyload) { 72 intersection(); 73 } 74 } 75 function loop(el) { 76 if (el instanceof HTMLElement === false || el.classList.contains('lazyframe--loaded')) return; 77 const lazyframe = { 78 el: el, 79 settings: setup(el) 80 }; 81 lazyframe.el.addEventListener('click', event => { 82 lazyframe.el.appendChild(lazyframe.iframe); 83 const iframe = el.querySelectorAll('iframe'); 84 lazyframe.settings.onAppend.call(this, iframe[0]); 85 lazyframe.el.dispatchEvent(new CustomEvent('lazyframe-appended', event)); 86 }); 87 if (settings.lazyload) { 88 build(lazyframe); 89 } else { 90 api(lazyframe, !!lazyframe.settings.thumbnail); 91 } 92 } 93 function setup(el) { 94 const attr = Array.prototype.slice.apply(el.attributes).filter(att => att.value !== '').reduce((obj, curr) => { 95 let name = curr.name.indexOf('data-') === 0 ? curr.name.split('data-')[1] : curr.name; 96 obj[name] = curr.value; 97 return obj; 98 }, {}); 99 const options = Object.assign({}, settings, attr, { 100 y: el.offsetTop 101 }); 102 if (options.vendor) { 103 const match = options.src.match(constants.regex[options.vendor]); 104 options.id = constants.condition[options.vendor](match); 105 } 106 return options; 107 } 108 function useApi(settings) { 109 if (!settings.vendor) return false; 110 return !settings.title || !settings.thumbnail; 111 } 112 function api(lazyframe) { 113 if (useApi(lazyframe.settings)) { 114 send(lazyframe, ([response, _l]) => { 115 if (!_l.settings.title) { 116 _l.settings.title = constants.response.title(response); 117 } 118 if (!_l.settings.thumbnail) { 119 const url = constants.response.thumbnail(response); 120 _l.settings.thumbnail = url; 121 lazyframe.settings.onThumbnailLoad.call(this, url); 122 } 123 build(_l, true); 124 }); 125 } else { 126 build(lazyframe, true); 127 } 128 } 129 function send(lazyframe, cb) { 130 const { 131 id: videoId = '', 132 src 133 } = lazyframe.settings; 134 const defaultData = { 135 title: "", 136 thumbnail_url: `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg` 137 }; 138 const endpoint = constants.endpoint(videoId ? `https://www.youtube.com/watch?v=${videoId}` : src); 139 const request = new XMLHttpRequest(); 140 request.open('GET', endpoint, true); 141 request.timeout = 500; 142 request.onload = function () { 143 if (request.status >= 200 && request.status < 400) { 144 const data = JSON.parse(request.responseText); 145 cb([data, lazyframe]); 146 } else { 147 cb([defaultData, lazyframe]); 148 } 149 }; 150 request.onerror = function () { 151 cb([defaultData, lazyframe]); 152 }; 153 request.ontimeout = function () { 154 cb([defaultData, lazyframe]); 155 }; 156 request.send(); 157 } 158 function getIframeID(elem) { 159 return elem.getElementsByTagName('template')[0].content.firstElementChild.getAttribute('id'); 160 } 161 function intersection() { 162 const mappedElements = elements.map(({ 163 el 164 }) => ({ 165 id: getIframeID(el), 166 el 167 })); 168 const handleIntersection = entries => {
169 entries.forEach(entry => { 170 if (entry.isIntersecting) { 171 const targetID = getIframeID(entry.target); 172 const elementIndex = mappedElements.findIndex(({ 173 id 174 }) => targetID === id); 175 // Set up the facade 176 elements[elementIndex].settings.initialized = true; 177 api(elements[elementIndex]); 178 entry.target.classList.add('lazyframe--loaded'); 179 observer.unobserve(entry.target); 180 } 181 }); 182 }; 183 const observer = new IntersectionObserver(handleIntersection); 184 mappedElements.forEach(({ 185 el 186 }) => observer.observe(el)); 187 } 188 function build(lazyframe, loadImage) { 189 const iframeAttrs = [...lazyframe.el.getElementsByTagName('template')[0].content.firstElementChild.attributes].map(({ 190 name, 191 value 192 }) => ({ 193 name, 194 value 195 })); 196 lazyframe.iframe = getIframe(lazyframe.settings, iframeAttrs); 197 if (lazyframe.settings.thumbnail && loadImage) { 198 lazyframe.el.style.backgroundImage = `url(${lazyframe.settings.thumbnail})`; 199 } 200 if (lazyframe.settings.title && lazyframe.el.children.length === 1) { 201 const docfrag = document.createDocumentFragment(), 202 titleNode = document.createElement('span'); 203 titleNode.className = 'lazyframe__title'; 204 titleNode.innerHTML = lazyframe.settings.title; 205 docfrag.appendChild(titleNode); 206 lazyframe.el.appendChild(docfrag); 207 } 208 if (!settings.lazyload) { 209 lazyframe.el.classList.add('lazyframe--loaded'); 210 lazyframe.settings.onLoad.call(this, lazyframe); 211 elements.push(lazyframe); 212 } 213 if (!lazyframe.settings.initialized) { 214 elements.push(lazyframe); 215 } 216 } 217 function getIframe(settings, iframeAttrs) { 218 const docfrag = document.createDocumentFragment(); 219 const iframeNode = document.createElement('iframe'); 220 if (settings.vendor && !settings.src.includes("autoplay")) { 221 settings.src = settings.src.includes('?') ? `${settings.src}&autoplay=1` : `${settings.src}?autoplay=1`; 222 } 223 iframeAttrs.forEach(({ 224 name, 225 value 226 }) => { 227 iframeNode.setAttribute(name, value); 228 }); 229 iframeNode.setAttribute('src', settings.src); 230 if (settings.autoplay) { 231 iframeNode.allow = 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture'; 232 } 233 docfrag.appendChild(iframeNode); 234 return docfrag; 235 } 236 return init; 237 }; 238 const lf = Lazyframe(); 239 240 return lf; 241 242}));
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.