/*! * Vue-Lazyload.js v1.3.4 * (c) 2021 Awe * Released under the MIT License. */ /*! * is-primitive * * Copyright (c) 2014-2015, Jon Schlinkert. * Licensed under the MIT License. */ // see http://jsperf.com/testing-value-is-primitive/7 var isPrimitive = function isPrimitive(value) { return value == null || typeof value !== 'function' && typeof value !== 'object'; }; var isPrimitive$1 = /*#__PURE__*/Object.freeze({ __proto__: null, 'default': isPrimitive, __moduleExports: isPrimitive }); /*! * assign-symbols * * Copyright (c) 2015, Jon Schlinkert. * Licensed under the MIT License. */ var assignSymbols = function (receiver, objects) { if (receiver === null || typeof receiver === 'undefined') { throw new TypeError('expected first argument to be an object.'); } if (typeof objects === 'undefined' || typeof Symbol === 'undefined') { return receiver; } if (typeof Object.getOwnPropertySymbols !== 'function') { return receiver; } var isEnumerable = Object.prototype.propertyIsEnumerable; var target = Object(receiver); var len = arguments.length, i = 0; while (++i < len) { var provider = Object(arguments[i]); var names = Object.getOwnPropertySymbols(provider); for (var j = 0; j < names.length; j++) { var key = names[j]; if (isEnumerable.call(provider, key)) { target[key] = provider[key]; } } } return target; }; var assignSymbols$1 = /*#__PURE__*/Object.freeze({ __proto__: null, 'default': assignSymbols, __moduleExports: assignSymbols }); var toString = Object.prototype.toString; /** * Get the native `typeof` a value. * * @param {*} `val` * @return {*} Native javascript type */ var kindOf = function kindOf(val) { var type = typeof val; // primitivies if (type === 'undefined') { return 'undefined'; } if (val === null) { return 'null'; } if (val === true || val === false || val instanceof Boolean) { return 'boolean'; } if (type === 'string' || val instanceof String) { return 'string'; } if (type === 'number' || val instanceof Number) { return 'number'; } // functions if (type === 'function' || val instanceof Function) { if (typeof val.constructor.name !== 'undefined' && val.constructor.name.slice(0, 9) === 'Generator') { return 'generatorfunction'; } return 'function'; } // array if (typeof Array.isArray !== 'undefined' && Array.isArray(val)) { return 'array'; } // check for instances of RegExp and Date before calling `toString` if (val instanceof RegExp) { return 'regexp'; } if (val instanceof Date) { return 'date'; } // other objects type = toString.call(val); if (type === '[object RegExp]') { return 'regexp'; } if (type === '[object Date]') { return 'date'; } if (type === '[object Arguments]') { return 'arguments'; } if (type === '[object Error]') { return 'error'; } if (type === '[object Promise]') { return 'promise'; } // buffer if (isBuffer(val)) { return 'buffer'; } // es6: Map, WeakMap, Set, WeakSet if (type === '[object Set]') { return 'set'; } if (type === '[object WeakSet]') { return 'weakset'; } if (type === '[object Map]') { return 'map'; } if (type === '[object WeakMap]') { return 'weakmap'; } if (type === '[object Symbol]') { return 'symbol'; } if (type === '[object Map Iterator]') { return 'mapiterator'; } if (type === '[object Set Iterator]') { return 'setiterator'; } if (type === '[object String Iterator]') { return 'stringiterator'; } if (type === '[object Array Iterator]') { return 'arrayiterator'; } // typed arrays if (type === '[object Int8Array]') { return 'int8array'; } if (type === '[object Uint8Array]') { return 'uint8array'; } if (type === '[object Uint8ClampedArray]') { return 'uint8clampedarray'; } if (type === '[object Int16Array]') { return 'int16array'; } if (type === '[object Uint16Array]') { return 'uint16array'; } if (type === '[object Int32Array]') { return 'int32array'; } if (type === '[object Uint32Array]') { return 'uint32array'; } if (type === '[object Float32Array]') { return 'float32array'; } if (type === '[object Float64Array]') { return 'float64array'; } // must be a plain object return 'object'; }; /** * If you need to support Safari 5-7 (8-10 yr-old browser), * take a look at https://github.com/feross/is-buffer */ function isBuffer(val) { return val.constructor && typeof val.constructor.isBuffer === 'function' && val.constructor.isBuffer(val); } var kindOf$1 = /*#__PURE__*/Object.freeze({ __proto__: null, 'default': kindOf, __moduleExports: kindOf }); var isPrimitive$2 = ( isPrimitive$1 && isPrimitive ) || isPrimitive$1; var assignSymbols$2 = ( assignSymbols$1 && assignSymbols ) || assignSymbols$1; var typeOf = ( kindOf$1 && kindOf ) || kindOf$1; function assign(target /*, objects*/) { target = target || {}; var len = arguments.length, i = 0; if (len === 1) { return target; } while (++i < len) { var val = arguments[i]; if (isPrimitive$2(target)) { target = val; } if (isObject(val)) { extend(target, val); } } return target; } /** * Shallow extend */ function extend(target, obj) { assignSymbols$2(target, obj); for (var key in obj) { if (isValidKey(key) && hasOwn(obj, key)) { var val = obj[key]; if (isObject(val)) { if (typeOf(target[key]) === 'undefined' && typeOf(val) === 'function') { target[key] = val; } target[key] = assign(target[key] || {}, val); } else { target[key] = val; } } } return target; } /** * Returns true if the object is a plain object or a function. */ function isObject(obj) { return typeOf(obj) === 'object' || typeOf(obj) === 'function'; } /** * Returns true if the given `key` is an own property of `obj`. */ function hasOwn(obj, key) { return Object.prototype.hasOwnProperty.call(obj, key); } /** * Returns true if the given `key` is a valid key that can be used for assigning properties. */ function isValidKey(key) { return key !== '__proto__' && key !== 'constructor' && key !== 'prototype'; } /** * Expose `assign` */ var assignDeep = assign; const inBrowser = typeof window !== 'undefined' && window !== null; const hasIntersectionObserver = checkIntersectionObserver(); function checkIntersectionObserver() { if (inBrowser && 'IntersectionObserver' in window && 'IntersectionObserverEntry' in window && 'intersectionRatio' in window.IntersectionObserverEntry.prototype) { // Minimal polyfill for Edge 15's lack of `isIntersecting` // See: https://github.com/w3c/IntersectionObserver/issues/211 if (!('isIntersecting' in window.IntersectionObserverEntry.prototype)) { Object.defineProperty(window.IntersectionObserverEntry.prototype, 'isIntersecting', { get: function () { return this.intersectionRatio > 0; } }); } return true; } return false; } const modeType = { event: 'event', observer: 'observer' // CustomEvent polyfill for IE };const CustomEvent = function () { if (!inBrowser) return; // not IE if (typeof window.CustomEvent === 'function') return window.CustomEvent; function CustomEvent(event, params) { params = params || { bubbles: false, cancelable: false, detail: undefined }; var evt = document.createEvent('CustomEvent'); evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); return evt; } CustomEvent.prototype = window.Event.prototype; return CustomEvent; }(); function remove(arr, item) { if (!arr.length) return; const index = arr.indexOf(item); if (index > -1) return arr.splice(index, 1); } function some(arr, fn) { let has = false; for (let i = 0, len = arr.length; i < len; i++) { if (fn(arr[i])) { has = true; break; } } return has; } function getBestSelectionFromSrcset(el, scale) { if (el.tagName !== 'IMG' || !el.getAttribute('data-srcset')) return; let options = el.getAttribute('data-srcset'); const result = []; const container = el.parentNode; const containerWidth = container.offsetWidth * scale; let spaceIndex; let tmpSrc; let tmpWidth; options = options.trim().split(','); options.map(item => { item = item.trim(); spaceIndex = item.lastIndexOf(' '); if (spaceIndex === -1) { tmpSrc = item; tmpWidth = 999998; } else { tmpSrc = item.substr(0, spaceIndex); tmpWidth = parseInt(item.substr(spaceIndex + 1, item.length - spaceIndex - 2), 10); } result.push([tmpWidth, tmpSrc]); }); result.sort(function (a, b) { if (a[0] < b[0]) { return 1; } if (a[0] > b[0]) { return -1; } if (a[0] === b[0]) { if (b[1].indexOf('.webp', b[1].length - 5) !== -1) { return 1; } if (a[1].indexOf('.webp', a[1].length - 5) !== -1) { return -1; } } return 0; }); let bestSelectedSrc = ''; let tmpOption; for (let i = 0; i < result.length; i++) { tmpOption = result[i]; bestSelectedSrc = tmpOption[1]; const next = result[i + 1]; if (next && next[0] < containerWidth) { bestSelectedSrc = tmpOption[1]; break; } else if (!next) { bestSelectedSrc = tmpOption[1]; break; } } return bestSelectedSrc; } function find(arr, fn) { let item; for (let i = 0, len = arr.length; i < len; i++) { if (fn(arr[i])) { item = arr[i]; break; } } return item; } const getDPR = (scale = 1) => inBrowser ? window.devicePixelRatio || scale : scale; function supportWebp() { if (!inBrowser) return false; let support = true; try { const elem = document.createElement('canvas'); if (elem.getContext && elem.getContext('2d')) { support = elem.toDataURL('image/webp').indexOf('data:image/webp') === 0; } } catch (err) { support = false; } return support; } function throttle(action, delay) { let timeout = null; let movement = null; let lastRun = 0; let needRun = false; return function () { needRun = true; if (timeout) { return; } let elapsed = Date.now() - lastRun; let context = this; let args = arguments; let runCallback = function () { lastRun = Date.now(); timeout = false; action.apply(context, args); }; if (elapsed >= delay) { runCallback(); } else { timeout = setTimeout(runCallback, delay); } if (needRun) { clearTimeout(movement); movement = setTimeout(runCallback, 2 * delay); } }; } function testSupportsPassive() { if (!inBrowser) return; let support = false; try { let opts = Object.defineProperty({}, 'passive', { get: function () { support = true; } }); window.addEventListener('test', null, opts); } catch (e) {} return support; } const supportsPassive = testSupportsPassive(); const _ = { on(el, type, func, capture = false) { if (supportsPassive) { el.addEventListener(type, func, { capture: capture, passive: true }); } else { el.addEventListener(type, func, capture); } }, off(el, type, func, capture = false) { el.removeEventListener(type, func, capture); } }; const loadImageAsync = (item, resolve, reject) => { let image = new Image(); if (!item || !item.src) { const err = new Error('image src is required'); return reject(err); } image.src = item.src; if (item.cors) { image.crossOrigin = item.cors; } image.onload = function () { resolve({ naturalHeight: image.naturalHeight, naturalWidth: image.naturalWidth, src: image.src }); }; image.onerror = function (e) { reject(e); }; }; const style = (el, prop) => { return typeof getComputedStyle !== 'undefined' ? getComputedStyle(el, null).getPropertyValue(prop) : el.style[prop]; }; const overflow = el => { return style(el, 'overflow') + style(el, 'overflow-y') + style(el, 'overflow-x'); }; const scrollParent = el => { if (!inBrowser) return; if (!(el instanceof HTMLElement)) { return window; } let parent = el; while (parent) { if (parent === document.body || parent === document.documentElement) { break; } if (!parent.parentNode) { break; } if (/(scroll|auto)/.test(overflow(parent))) { return parent; } parent = parent.parentNode; } return window; }; function isObject$1(obj) { return obj !== null && typeof obj === 'object'; } function ObjectKeys(obj) { if (!(obj instanceof Object)) return []; if (Object.keys) { return Object.keys(obj); } else { let keys = []; for (let key in obj) { if (obj.hasOwnProperty(key)) { keys.push(key); } } return keys; } } function ArrayFrom(arrLike) { let len = arrLike.length; const list = []; for (let i = 0; i < len; i++) { list.push(arrLike[i]); } return list; } function noop() {} class ImageCache { constructor({ max }) { this.options = { max: max || 100 }; this._caches = []; } has(key) { return this._caches.indexOf(key) > -1; } add(key) { if (this.has(key)) return; this._caches.push(key); if (this._caches.length > this.options.max) { this.free(); } } free() { this._caches.shift(); } } // el: { // state, // src, // error, // loading // } class ReactiveListener { constructor({ el, src, error, loading, bindType, $parent, options, cors, elRenderer, imageCache }) { this.el = el; this.src = src; this.error = error; this.loading = loading; this.bindType = bindType; this.attempt = 0; this.cors = cors; this.naturalHeight = 0; this.naturalWidth = 0; this.options = options; this.rect = null; this.$parent = $parent; this.elRenderer = elRenderer; this._imageCache = imageCache; this.performanceData = { init: Date.now(), loadStart: 0, loadEnd: 0 }; this.filter(); this.initState(); this.render('loading', false); } /* * init listener state * @return */ initState() { if ('dataset' in this.el) { this.el.dataset.src = this.src; } else { this.el.setAttribute('data-src', this.src); } this.state = { loading: false, error: false, loaded: false, rendered: false }; } /* * record performance * @return */ record(event) { this.performanceData[event] = Date.now(); } /* * update image listener data * @param {String} image uri * @param {String} loading image uri * @param {String} error image uri * @return */ update({ src, loading, error }) { const oldSrc = this.src; this.src = src; this.loading = loading; this.error = error; this.filter(); if (oldSrc !== this.src) { this.attempt = 0; this.initState(); } } /* * get el node rect * @return */ getRect() { this.rect = this.el.getBoundingClientRect(); } /* * check el is in view * @return {Boolean} el is in view */ checkInView() { this.getRect(); return this.rect.top < window.innerHeight * this.options.preLoad && this.rect.bottom > this.options.preLoadTop && this.rect.left < window.innerWidth * this.options.preLoad && this.rect.right > 0; } /* * listener filter */ filter() { ObjectKeys(this.options.filter).map(key => { this.options.filter[key](this, this.options); }); } /* * render loading first * @params cb:Function * @return */ renderLoading(cb) { this.state.loading = true; loadImageAsync({ src: this.loading, cors: this.cors }, data => { this.render('loading', false); this.state.loading = false; cb(); }, () => { // handler `loading image` load failed cb(); this.state.loading = false; if (!this.options.silent) console.warn(`VueLazyload log: load failed with loading image(${this.loading})`); }); } /* * try load image and render it * @return */ load(onFinish = noop) { if (this.attempt > this.options.attempt - 1 && this.state.error) { if (!this.options.silent) console.log(`VueLazyload log: ${this.src} tried too more than ${this.options.attempt} times`); onFinish(); return; } if (this.state.rendered && this.state.loaded) return; if (this._imageCache.has(this.src)) { this.state.loaded = true; this.render('loaded', true); this.state.rendered = true; return onFinish(); } this.renderLoading(() => { this.attempt++; this.options.adapter['beforeLoad'] && this.options.adapter['beforeLoad'](this, this.options); this.record('loadStart'); loadImageAsync({ src: this.src, cors: this.cors }, data => { this.naturalHeight = data.naturalHeight; this.naturalWidth = data.naturalWidth; this.state.loaded = true; this.state.error = false; this.record('loadEnd'); this.render('loaded', false); this.state.rendered = true; this._imageCache.add(this.src); onFinish(); }, err => { !this.options.silent && console.error(err); this.state.error = true; this.state.loaded = false; this.render('error', false); }); }); } /* * render image * @param {String} state to render // ['loading', 'src', 'error'] * @param {String} is form cache * @return */ render(state, cache) { this.elRenderer(this, state, cache); } /* * output performance data * @return {Object} performance data */ performance() { let state = 'loading'; let time = 0; if (this.state.loaded) { state = 'loaded'; time = (this.performanceData.loadEnd - this.performanceData.loadStart) / 1000; } if (this.state.error) state = 'error'; return { src: this.src, state, time }; } /* * $destroy * @return */ $destroy() { this.el = null; this.src = null; this.error = null; this.loading = null; this.bindType = null; this.attempt = 0; } } const DEFAULT_URL = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; const DEFAULT_EVENTS = ['scroll', 'wheel', 'mousewheel', 'resize', 'animationend', 'transitionend', 'touchmove']; const DEFAULT_OBSERVER_OPTIONS = { rootMargin: '0px', threshold: 0 }; function Lazy(Vue) { return class Lazy { constructor({ preLoad, error, throttleWait, preLoadTop, dispatchEvent, loading, attempt, silent = true, scale, listenEvents, hasbind, filter, adapter, observer, observerOptions }) { this.version = '"1.3.4"'; this.mode = modeType.event; this.ListenerQueue = []; this.TargetIndex = 0; this.TargetQueue = []; this.options = { silent: silent, dispatchEvent: !!dispatchEvent, throttleWait: throttleWait || 200, preLoad: preLoad || 1.3, preLoadTop: preLoadTop || 0, error: error || DEFAULT_URL, loading: loading || DEFAULT_URL, attempt: attempt || 3, scale: scale || getDPR(scale), ListenEvents: listenEvents || DEFAULT_EVENTS, hasbind: false, supportWebp: supportWebp(), filter: filter || {}, adapter: adapter || {}, observer: !!observer, observerOptions: observerOptions || DEFAULT_OBSERVER_OPTIONS }; this._initEvent(); this._imageCache = new ImageCache({ max: 200 }); this.lazyLoadHandler = throttle(this._lazyLoadHandler.bind(this), this.options.throttleWait); this.setMode(this.options.observer ? modeType.observer : modeType.event); } /** * update config * @param {Object} config params * @return */ config(options = {}) { assignDeep(this.options, options); } /** * output listener's load performance * @return {Array} */ performance() { let list = []; this.ListenerQueue.map(item => { list.push(item.performance()); }); return list; } /* * add lazy component to queue * @param {Vue} vm lazy component instance * @return */ addLazyBox(vm) { this.ListenerQueue.push(vm); if (inBrowser) { this._addListenerTarget(window); this._observer && this._observer.observe(vm.el); if (vm.$el && vm.$el.parentNode) { this._addListenerTarget(vm.$el.parentNode); } } } /* * add image listener to queue * @param {DOM} el * @param {object} binding vue directive binding * @param {vnode} vnode vue directive vnode * @return */ add(el, binding, vnode) { if (some(this.ListenerQueue, item => item.el === el)) { this.update(el, binding); return Vue.nextTick(this.lazyLoadHandler); } let { src, loading, error, cors } = this._valueFormatter(binding.value); Vue.nextTick(() => { src = getBestSelectionFromSrcset(el, this.options.scale) || src; this._observer && this._observer.observe(el); const container = Object.keys(binding.modifiers)[0]; let $parent; if (container) { $parent = vnode.context.$refs[container]; // if there is container passed in, try ref first, then fallback to getElementById to support the original usage $parent = $parent ? $parent.$el || $parent : document.getElementById(container); } if (!$parent) { $parent = scrollParent(el); } const newListener = new ReactiveListener({ bindType: binding.arg, $parent, el, loading, error, src, cors, elRenderer: this._elRenderer.bind(this), options: this.options, imageCache: this._imageCache }); this.ListenerQueue.push(newListener); if (inBrowser) { this._addListenerTarget(window); this._addListenerTarget($parent); } this.lazyLoadHandler(); Vue.nextTick(() => this.lazyLoadHandler()); }); } /** * update image src * @param {DOM} el * @param {object} vue directive binding * @return */ update(el, binding, vnode) { let { src, loading, error } = this._valueFormatter(binding.value); src = getBestSelectionFromSrcset(el, this.options.scale) || src; const exist = find(this.ListenerQueue, item => item.el === el); if (!exist) { this.add(el, binding, vnode); } else { exist.update({ src, loading, error }); } if (this._observer) { this._observer.unobserve(el); this._observer.observe(el); } this.lazyLoadHandler(); Vue.nextTick(() => this.lazyLoadHandler()); } /** * remove listener form list * @param {DOM} el * @return */ remove(el) { if (!el) return; this._observer && this._observer.unobserve(el); const existItem = find(this.ListenerQueue, item => item.el === el); if (existItem) { this._removeListenerTarget(existItem.$parent); this._removeListenerTarget(window); remove(this.ListenerQueue, existItem); existItem.$destroy(); } } /* * remove lazy components form list * @param {Vue} vm Vue instance * @return */ removeComponent(vm) { if (!vm) return; remove(this.ListenerQueue, vm); this._observer && this._observer.unobserve(vm.el); if (vm.$parent && vm.$el.parentNode) { this._removeListenerTarget(vm.$el.parentNode); } this._removeListenerTarget(window); } setMode(mode) { if (!hasIntersectionObserver && mode === modeType.observer) { mode = modeType.event; } this.mode = mode; // event or observer if (mode === modeType.event) { if (this._observer) { this.ListenerQueue.forEach(listener => { this._observer.unobserve(listener.el); }); this._observer = null; } this.TargetQueue.forEach(target => { this._initListen(target.el, true); }); } else { this.TargetQueue.forEach(target => { this._initListen(target.el, false); }); this._initIntersectionObserver(); } } /* *** Private functions *** */ /* * add listener target * @param {DOM} el listener target * @return */ _addListenerTarget(el) { if (!el) return; let target = find(this.TargetQueue, target => target.el === el); if (!target) { target = { el: el, id: ++this.TargetIndex, childrenCount: 1, listened: true }; this.mode === modeType.event && this._initListen(target.el, true); this.TargetQueue.push(target); } else { target.childrenCount++; } return this.TargetIndex; } /* * remove listener target or reduce target childrenCount * @param {DOM} el or window * @return */ _removeListenerTarget(el) { this.TargetQueue.forEach((target, index) => { if (target.el === el) { target.childrenCount--; if (!target.childrenCount) { this._initListen(target.el, false); this.TargetQueue.splice(index, 1); target = null; } } }); } /* * add or remove eventlistener * @param {DOM} el DOM or Window * @param {boolean} start flag * @return */ _initListen(el, start) { this.options.ListenEvents.forEach(evt => _[start ? 'on' : 'off'](el, evt, this.lazyLoadHandler)); } _initEvent() { this.Event = { listeners: { loading: [], loaded: [], error: [] } }; this.$on = (event, func) => { if (!this.Event.listeners[event]) this.Event.listeners[event] = []; this.Event.listeners[event].push(func); }; this.$once = (event, func) => { const vm = this; function on() { vm.$off(event, on); func.apply(vm, arguments); } this.$on(event, on); }; this.$off = (event, func) => { if (!func) { if (!this.Event.listeners[event]) return; this.Event.listeners[event].length = 0; return; } remove(this.Event.listeners[event], func); }; this.$emit = (event, context, inCache) => { if (!this.Event.listeners[event]) return; this.Event.listeners[event].forEach(func => func(context, inCache)); }; } /** * find nodes which in viewport and trigger load * @return */ _lazyLoadHandler() { const freeList = []; this.ListenerQueue.forEach((listener, index) => { if (!listener.el || !listener.el.parentNode) { freeList.push(listener); } const catIn = listener.checkInView(); if (!catIn) return; listener.load(); }); freeList.forEach(item => { remove(this.ListenerQueue, item); item.$destroy(); }); } /** * init IntersectionObserver * set mode to observer * @return */ _initIntersectionObserver() { if (!hasIntersectionObserver) return; this._observer = new IntersectionObserver(this._observerHandler.bind(this), this.options.observerOptions); if (this.ListenerQueue.length) { this.ListenerQueue.forEach(listener => { this._observer.observe(listener.el); }); } } /** * init IntersectionObserver * @return */ _observerHandler(entries, observer) { entries.forEach(entry => { if (entry.isIntersecting) { this.ListenerQueue.forEach(listener => { if (listener.el === entry.target) { if (listener.state.loaded) return this._observer.unobserve(listener.el); listener.load(); } }); } }); } /** * set element attribute with image'url and state * @param {object} lazyload listener object * @param {string} state will be rendered * @param {bool} inCache is rendered from cache * @return */ _elRenderer(listener, state, cache) { if (!listener.el) return; const { el, bindType } = listener; let src; switch (state) { case 'loading': src = listener.loading; break; case 'error': src = listener.error; break; default: src = listener.src; break; } if (bindType) { el.style[bindType] = 'url("' + src + '")'; } else if (el.getAttribute('src') !== src) { el.setAttribute('src', src); } el.setAttribute('lazy', state); this.$emit(state, listener, cache); this.options.adapter[state] && this.options.adapter[state](listener, this.options); if (this.options.dispatchEvent) { const event = new CustomEvent(state, { detail: listener }); el.dispatchEvent(event); } } /** * generate loading loaded error image url * @param {string} image's src * @return {object} image's loading, loaded, error url */ _valueFormatter(value) { let src = value; let loading = this.options.loading; let error = this.options.error; // value is object if (isObject$1(value)) { if (!value.src && !this.options.silent) console.error('Vue Lazyload warning: miss src with ' + value); src = value.src; loading = value.loading || this.options.loading; error = value.error || this.options.error; } return { src, loading, error }; } }; } Lazy.install = (Vue, options = {}) => { const LazyClass = Lazy(Vue); const lazy = new LazyClass(options); const isVue2 = Vue.version.split('.')[0] === '2'; if (isVue2) { Vue.directive('lazy', { bind: lazy.add.bind(lazy), update: lazy.update.bind(lazy), componentUpdated: lazy.lazyLoadHandler.bind(lazy), unbind: lazy.remove.bind(lazy) }); } else { Vue.directive('lazy', { bind: lazy.lazyLoadHandler.bind(lazy), update(newValue, oldValue) { assignDeep(this.vm.$refs, this.vm.$els); lazy.add(this.el, { modifiers: this.modifiers || {}, arg: this.arg, value: newValue, oldValue: oldValue }, { context: this.vm }); }, unbind() { lazy.remove(this.el); } }); } }; const LazyComponent = lazy => { return { props: { tag: { type: String, default: 'div' } }, render(h) { return h(this.tag, null, this.show ? this.$slots.default : null); }, data() { return { el: null, state: { loaded: false }, rect: {}, show: false }; }, mounted() { this.el = this.$el; lazy.addLazyBox(this); lazy.lazyLoadHandler(); }, beforeDestroy() { lazy.removeComponent(this); }, methods: { getRect() { this.rect = this.$el.getBoundingClientRect(); }, checkInView() { this.getRect(); return inBrowser && this.rect.top < window.innerHeight * lazy.options.preLoad && this.rect.bottom > 0 && this.rect.left < window.innerWidth * lazy.options.preLoad && this.rect.right > 0; }, load() { this.show = true; this.state.loaded = true; this.$emit('show', this); }, destroy() { return this.$destroy; } } }; }; LazyComponent.install = function (Vue, options = {}) { const LazyClass = Lazy(Vue); const lazy = new LazyClass(options); Vue.component('lazy-component', LazyComponent(lazy)); }; class LazyContainerMananger { constructor({ lazy }) { this.lazy = lazy; lazy.lazyContainerMananger = this; this._queue = []; } bind(el, binding, vnode) { const container = new LazyContainer({ el, binding, vnode, lazy: this.lazy }); this._queue.push(container); } update(el, binding, vnode) { const container = find(this._queue, item => item.el === el); if (!container) return; container.update({ el, binding, vnode }); } unbind(el, binding, vnode) { const container = find(this._queue, item => item.el === el); if (!container) return; container.clear(); remove(this._queue, container); } } const defaultOptions = { selector: 'img' }; class LazyContainer { constructor({ el, binding, vnode, lazy }) { this.el = null; this.vnode = vnode; this.binding = binding; this.options = {}; this.lazy = lazy; this._queue = []; this.update({ el, binding }); } update({ el, binding }) { this.el = el; this.options = assignDeep({}, defaultOptions, binding.value); const imgs = this.getImgs(); imgs.forEach(el => { this.lazy.add(el, assignDeep({}, this.binding, { value: { src: 'dataset' in el ? el.dataset.src : el.getAttribute('data-src'), error: ('dataset' in el ? el.dataset.error : el.getAttribute('data-error')) || this.options.error, loading: ('dataset' in el ? el.dataset.loading : el.getAttribute('data-loading')) || this.options.loading } }), this.vnode); }); } getImgs() { return ArrayFrom(this.el.querySelectorAll(this.options.selector)); } clear() { const imgs = this.getImgs(); imgs.forEach(el => this.lazy.remove(el)); this.vnode = null; this.binding = null; this.lazy = null; } } LazyContainer.install = (Vue, options = {}) => { const LazyClass = Lazy(Vue); const lazy = new LazyClass(options); const lazyContainer = new LazyContainer({ lazy }); const isVue2 = Vue.version.split('.')[0] === '2'; if (isVue2) { Vue.directive('lazy-container', { bind: lazyContainer.bind.bind(lazyContainer), componentUpdated: lazyContainer.update.bind(lazyContainer), unbind: lazyContainer.unbind.bind(lazyContainer) }); } else { Vue.directive('lazy-container', { update(newValue, oldValue) { lazyContainer.update(this.el, { modifiers: this.modifiers || {}, arg: this.arg, value: newValue, oldValue: oldValue }, { context: this.vm }); }, unbind() { lazyContainer.unbind(this.el); } }); } }; const LazyImage = lazyManager => { return { props: { src: [String, Object], tag: { type: String, default: 'img' } }, render(h) { return h(this.tag, { attrs: { src: this.renderSrc } }, this.$slots.default); }, data() { return { el: null, options: { src: '', error: '', loading: '', attempt: lazyManager.options.attempt }, state: { loaded: false, error: false, attempt: 0 }, rect: {}, renderSrc: '' }; }, watch: { src() { this.init(); lazyManager.addLazyBox(this); lazyManager.lazyLoadHandler(); } }, created() { this.init(); this.renderSrc = this.options.loading; }, mounted() { this.el = this.$el; lazyManager.addLazyBox(this); lazyManager.lazyLoadHandler(); }, beforeDestroy() { lazyManager.removeComponent(this); }, methods: { init() { const { src, loading, error } = lazyManager._valueFormatter(this.src); this.state.loaded = false; this.options.src = src; this.options.error = error; this.options.loading = loading; this.renderSrc = this.options.loading; }, getRect() { this.rect = this.$el.getBoundingClientRect(); }, checkInView() { this.getRect(); return inBrowser && this.rect.top < window.innerHeight * lazyManager.options.preLoad && this.rect.bottom > 0 && this.rect.left < window.innerWidth * lazyManager.options.preLoad && this.rect.right > 0; }, load(onFinish = noop) { if (this.state.attempt > this.options.attempt - 1 && this.state.error) { if (!lazyManager.options.silent) console.log(`VueLazyload log: ${this.options.src} tried too more than ${this.options.attempt} times`); onFinish(); return; } const src = this.options.src; loadImageAsync({ src }, ({ src }) => { this.renderSrc = src; this.state.loaded = true; }, e => { this.state.attempt++; this.renderSrc = this.options.error; this.state.error = true; }); } } }; }; LazyImage.install = (Vue, options = {}) => { const LazyClass = Lazy(Vue); const lazy = new LazyClass(options); Vue.component('lazy-image', LazyImage(lazy)); }; var index = { /* * install function * @param {Vue} Vue * @param {object} options lazyload options */ install(Vue, options = {}) { const LazyClass = Lazy(Vue); const lazy = new LazyClass(options); const lazyContainer = new LazyContainerMananger({ lazy }); const isVue2 = Vue.version.split('.')[0] === '2'; Vue.prototype.$Lazyload = lazy; if (options.lazyComponent) { Vue.component('lazy-component', LazyComponent(lazy)); } if (options.lazyImage) { Vue.component('lazy-image', LazyImage(lazy)); } if (isVue2) { Vue.directive('lazy', { bind: lazy.add.bind(lazy), update: lazy.update.bind(lazy), componentUpdated: lazy.lazyLoadHandler.bind(lazy), unbind: lazy.remove.bind(lazy) }); Vue.directive('lazy-container', { bind: lazyContainer.bind.bind(lazyContainer), componentUpdated: lazyContainer.update.bind(lazyContainer), unbind: lazyContainer.unbind.bind(lazyContainer) }); } else { Vue.directive('lazy', { bind: lazy.lazyLoadHandler.bind(lazy), update(newValue, oldValue) { assignDeep(this.vm.$refs, this.vm.$els); lazy.add(this.el, { modifiers: this.modifiers || {}, arg: this.arg, value: newValue, oldValue: oldValue }, { context: this.vm }); }, unbind() { lazy.remove(this.el); } }); Vue.directive('lazy-container', { update(newValue, oldValue) { lazyContainer.update(this.el, { modifiers: this.modifiers || {}, arg: this.arg, value: newValue, oldValue: oldValue }, { context: this.vm }); }, unbind() { lazyContainer.unbind(this.el); } }); } } }; export default index; export { Lazy, LazyComponent, LazyContainerMananger as LazyContainer, LazyImage };