UNPKG

framework7

Version:

Full featured mobile HTML framework for building iOS & Android apps

261 lines (241 loc) 7.63 kB
import $ from 'dom7'; import { window } from 'ssr-window'; import Utils from '../../utils/utils'; import Support from '../../utils/support'; const Lazy = { destroy(pageEl) { const $pageEl = $(pageEl).closest('.page'); if (!$pageEl.length) return; if ($pageEl[0].f7LazyDestroy) { $pageEl[0].f7LazyDestroy(); } }, create(pageEl) { const app = this; const $pageEl = $(pageEl).closest('.page').eq(0); // Lazy images const $lazyLoadImages = $pageEl.find('.lazy'); if ($lazyLoadImages.length === 0 && !$pageEl.hasClass('lazy')) return; // Placeholder const placeholderSrc = app.params.lazy.placeholder; if (placeholderSrc !== false) { $lazyLoadImages.each((index, lazyEl) => { if ($(lazyEl).attr('data-src') && !$(lazyEl).attr('src')) $(lazyEl).attr('src', placeholderSrc); }); } // load image const imagesSequence = []; let imageIsLoading = false; function onImageComplete(lazyEl) { if (imagesSequence.indexOf(lazyEl) >= 0) { imagesSequence.splice(imagesSequence.indexOf(lazyEl), 1); } imageIsLoading = false; if (app.params.lazy.sequential && imagesSequence.length > 0) { imageIsLoading = true; app.lazy.loadImage(imagesSequence[0], onImageComplete); } } function observerCallback(entries, observer) { entries.forEach((entry) => { if (entry.isIntersecting) { if (app.params.lazy.sequential && imageIsLoading) { if (imagesSequence.indexOf(entry.target) < 0) imagesSequence.push(entry.target); return; } // Load image imageIsLoading = true; app.lazy.loadImage(entry.target, onImageComplete); // Detach observer.unobserve(entry.target); } }); } if (app.params.lazy.observer && Support.intersectionObserver) { let observer = $pageEl[0].f7LazyObserver; if (!observer) { observer = new window.IntersectionObserver(observerCallback, { root: $pageEl[0], }); } $lazyLoadImages.each((index, el) => { if (el.f7LazyObserverAdded) return; el.f7LazyObserverAdded = true; observer.observe(el); }); if (!$pageEl[0].f7LazyDestroy) { $pageEl[0].f7LazyDestroy = () => { observer.disconnect(); delete $pageEl[0].f7LazyDestroy; delete $pageEl[0].f7LazyObserver; }; } return; } function lazyHandler() { app.lazy.load($pageEl, (lazyEl) => { if (app.params.lazy.sequential && imageIsLoading) { if (imagesSequence.indexOf(lazyEl) < 0) imagesSequence.push(lazyEl); return; } imageIsLoading = true; app.lazy.loadImage(lazyEl, onImageComplete); }); } function attachEvents() { $pageEl[0].f7LazyAttached = true; $pageEl.on('lazy', lazyHandler); $pageEl.on('scroll', lazyHandler, true); $pageEl.find('.tab').on('tab:mounted tab:show', lazyHandler); app.on('resize', lazyHandler); } function detachEvents() { $pageEl[0].f7LazyAttached = false; delete $pageEl[0].f7LazyAttached; $pageEl.off('lazy', lazyHandler); $pageEl.off('scroll', lazyHandler, true); $pageEl.find('.tab').off('tab:mounted tab:show', lazyHandler); app.off('resize', lazyHandler); } // Store detach function if (!$pageEl[0].f7LazyDestroy) { $pageEl[0].f7LazyDestroy = detachEvents; } // Attach events if (!$pageEl[0].f7LazyAttached) { attachEvents(); } // Run loader on page load/init lazyHandler(); }, isInViewport(lazyEl) { const app = this; const rect = lazyEl.getBoundingClientRect(); const threshold = app.params.lazy.threshold || 0; return ( rect.top >= (0 - threshold) && rect.left >= (0 - threshold) && rect.top <= (app.height + threshold) && rect.left <= (app.width + threshold) ); }, loadImage(imageEl, callback) { const app = this; const $imageEl = $(imageEl); const bg = $imageEl.attr('data-background'); const src = bg || $imageEl.attr('data-src'); function onLoad() { $imageEl.removeClass('lazy').addClass('lazy-loaded'); if (bg) { $imageEl.css('background-image', `url(${src})`); } else if (src) { $imageEl.attr('src', src); } if (callback) callback(imageEl); $imageEl.trigger('lazy:loaded'); app.emit('lazyLoaded', $imageEl[0]); } if (!src) { $imageEl.trigger('lazy:load'); app.emit('lazyLoad', $imageEl[0]); onLoad(); return; } function onError() { $imageEl.removeClass('lazy').addClass('lazy-loaded'); if (bg) { $imageEl.css('background-image', `url(${app.params.lazy.placeholder || ''})`); } else { $imageEl.attr('src', app.params.lazy.placeholder || ''); } if (callback) callback(imageEl); $imageEl.trigger('lazy:error'); app.emit('lazyError', $imageEl[0]); } const image = new window.Image(); image.onload = onLoad; image.onerror = onError; image.src = src; $imageEl.removeAttr('data-src').removeAttr('data-background'); // Add loaded callback and events $imageEl.trigger('lazy:load'); app.emit('lazyLoad', $imageEl[0]); }, load(pageEl, callback) { const app = this; let $pageEl = $(pageEl); if (!$pageEl.hasClass('page')) $pageEl = $pageEl.parents('.page').eq(0); if ($pageEl.length === 0) { return; } $pageEl.find('.lazy').each((index, lazyEl) => { const $lazyEl = $(lazyEl); if ($lazyEl.parents('.tab:not(.tab-active)').length > 0) { return; } if (app.lazy.isInViewport(lazyEl)) { if (callback) callback(lazyEl); else app.lazy.loadImage(lazyEl); } }); }, }; export default { name: 'lazy', params: { lazy: { placeholder: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEXCwsK592mkAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==', threshold: 0, sequential: true, observer: true, }, }, create() { const app = this; Utils.extend(app, { lazy: { create: Lazy.create.bind(app), destroy: Lazy.destroy.bind(app), loadImage: Lazy.loadImage.bind(app), load: Lazy.load.bind(app), isInViewport: Lazy.isInViewport.bind(app), }, }); }, on: { pageInit(page) { const app = this; if (page.$el.find('.lazy').length > 0 || page.$el.hasClass('lazy')) { app.lazy.create(page.$el); } }, pageAfterIn(page) { const app = this; if (app.params.lazy.observer && Support.intersectionObserver) return; if (page.$el.find('.lazy').length > 0 || page.$el.hasClass('lazy')) { app.lazy.create(page.$el); } }, pageBeforeRemove(page) { const app = this; if (page.$el.find('.lazy').length > 0 || page.$el.hasClass('lazy')) { app.lazy.destroy(page.$el); } }, tabMounted(tabEl) { const app = this; const $tabEl = $(tabEl); if ($tabEl.find('.lazy').length > 0 || $tabEl.hasClass('lazy')) { app.lazy.create($tabEl); } }, tabBeforeRemove(tabEl) { const app = this; if (app.params.lazy.observer && Support.intersectionObserver) return; const $tabEl = $(tabEl); if ($tabEl.find('.lazy').length > 0 || $tabEl.hasClass('lazy')) { app.lazy.destroy($tabEl); } }, }, };