You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1806 lines
44 KiB

/*!
* Vue-Lazyload.js v1.3.3
* (c) 2019 Awe <hilongjw@gmail.com>
* Released under the MIT License.
*/
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
/*!
* is-primitive <https://github.com/jonschlinkert/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 === 'undefined' ? 'undefined' : _typeof(value)) !== 'object';
};
/*!
* assign-symbols <https://github.com/jonschlinkert/assign-symbols>
*
* Copyright (c) 2015, Jon Schlinkert.
* Licensed under the MIT License.
*/
var assignSymbols = function assignSymbols(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 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 === 'undefined' ? 'undefined' : _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);
}
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(target)) {
target = val;
}
if (isObject$1(val)) {
extend(target, val);
}
}
return target;
}
/**
* Shallow extend
*/
function extend(target, obj) {
assignSymbols(target, obj);
for (var key in obj) {
if (key !== '__proto__' && hasOwn(obj, key)) {
var val = obj[key];
if (isObject$1(val)) {
if (kindOf(target[key]) === 'undefined' && kindOf(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$1(obj) {
return kindOf(obj) === 'object' || kindOf(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);
}
/**
* Expose `assign`
*/
var assignDeep = assign;
var inBrowser = typeof window !== 'undefined';
var 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 get$$1() {
return this.intersectionRatio > 0;
}
});
}
return true;
}
return false;
}
var modeType = {
event: 'event',
observer: 'observer'
// CustomEvent polyfill
};var CustomEvent = function () {
if (!inBrowser) return;
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;
var index = arr.indexOf(item);
if (index > -1) return arr.splice(index, 1);
}
function some(arr, fn) {
var has = false;
for (var 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;
var options = el.getAttribute('data-srcset');
var result = [];
var container = el.parentNode;
var containerWidth = container.offsetWidth * scale;
var spaceIndex = void 0;
var tmpSrc = void 0;
var tmpWidth = void 0;
options = options.trim().split(',');
options.map(function (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;
});
var bestSelectedSrc = '';
var tmpOption = void 0;
for (var i = 0; i < result.length; i++) {
tmpOption = result[i];
bestSelectedSrc = tmpOption[1];
var 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) {
var item = void 0;
for (var i = 0, len = arr.length; i < len; i++) {
if (fn(arr[i])) {
item = arr[i];
break;
}
}
return item;
}
var getDPR = function getDPR() {
var scale = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
return inBrowser ? window.devicePixelRatio || scale : scale;
};
function supportWebp() {
if (!inBrowser) return false;
var support = true;
var d = document;
try {
var el = d.createElement('object');
el.type = 'image/webp';
el.style.visibility = 'hidden';
el.innerHTML = '!';
d.body.appendChild(el);
support = !el.offsetWidth;
d.body.removeChild(el);
} catch (err) {
support = false;
}
return support;
}
function throttle(action, delay) {
var timeout = null;
var lastRun = 0;
return function () {
if (timeout) {
return;
}
var elapsed = Date.now() - lastRun;
var context = this;
var args = arguments;
var runCallback = function runCallback() {
lastRun = Date.now();
timeout = false;
action.apply(context, args);
};
if (elapsed >= delay) {
runCallback();
} else {
timeout = setTimeout(runCallback, delay);
}
};
}
function testSupportsPassive() {
if (!inBrowser) return;
var support = false;
try {
var opts = Object.defineProperty({}, 'passive', {
get: function get$$1() {
support = true;
}
});
window.addEventListener('test', null, opts);
} catch (e) {}
return support;
}
var supportsPassive = testSupportsPassive();
var _ = {
on: function on(el, type, func) {
var capture = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
if (supportsPassive) {
el.addEventListener(type, func, {
capture: capture,
passive: true
});
} else {
el.addEventListener(type, func, capture);
}
},
off: function off(el, type, func) {
var capture = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
el.removeEventListener(type, func, capture);
}
};
var loadImageAsync = function loadImageAsync(item, resolve, reject) {
var image = new Image();
if (!item || !item.src) {
var err = new Error('image src is required');
return reject(err);
}
image.src = item.src;
image.onload = function () {
resolve({
naturalHeight: image.naturalHeight,
naturalWidth: image.naturalWidth,
src: image.src
});
};
image.onerror = function (e) {
reject(e);
};
};
var style = function style(el, prop) {
return typeof getComputedStyle !== 'undefined' ? getComputedStyle(el, null).getPropertyValue(prop) : el.style[prop];
};
var overflow = function overflow(el) {
return style(el, 'overflow') + style(el, 'overflow-y') + style(el, 'overflow-x');
};
var scrollParent = function scrollParent(el) {
if (!inBrowser) return;
if (!(el instanceof HTMLElement)) {
return window;
}
var 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(obj) {
return obj !== null && (typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === 'object';
}
function ObjectKeys(obj) {
if (!(obj instanceof Object)) return [];
if (Object.keys) {
return Object.keys(obj);
} else {
var keys = [];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
keys.push(key);
}
}
return keys;
}
}
function ArrayFrom(arrLike) {
var len = arrLike.length;
var list = [];
for (var i = 0; i < len; i++) {
list.push(arrLike[i]);
}
return list;
}
function noop() {}
var ImageCache = function () {
function ImageCache(_ref) {
var max = _ref.max;
classCallCheck(this, ImageCache);
this.options = {
max: max || 100
};
this._caches = [];
}
createClass(ImageCache, [{
key: 'has',
value: function has(key) {
return this._caches.indexOf(key) > -1;
}
}, {
key: 'add',
value: function add(key) {
if (this.has(key)) return;
this._caches.push(key);
if (this._caches.length > this.options.max) {
this.free();
}
}
}, {
key: 'free',
value: function free() {
this._caches.shift();
}
}]);
return ImageCache;
}();
// el: {
// state,
// src,
// error,
// loading
// }
var ReactiveListener = function () {
function ReactiveListener(_ref) {
var el = _ref.el,
src = _ref.src,
error = _ref.error,
loading = _ref.loading,
bindType = _ref.bindType,
$parent = _ref.$parent,
options = _ref.options,
elRenderer = _ref.elRenderer,
imageCache = _ref.imageCache;
classCallCheck(this, ReactiveListener);
this.el = el;
this.src = src;
this.error = error;
this.loading = loading;
this.bindType = bindType;
this.attempt = 0;
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
*/
createClass(ReactiveListener, [{
key: 'initState',
value: function 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
*/
}, {
key: 'record',
value: function 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
*/
}, {
key: 'update',
value: function update(_ref2) {
var src = _ref2.src,
loading = _ref2.loading,
error = _ref2.error;
var 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
*/
}, {
key: 'getRect',
value: function getRect() {
this.rect = this.el.getBoundingClientRect();
}
/*
* check el is in view
* @return {Boolean} el is in view
*/
}, {
key: 'checkInView',
value: function 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
*/
}, {
key: 'filter',
value: function filter() {
var _this = this;
ObjectKeys(this.options.filter).map(function (key) {
_this.options.filter[key](_this, _this.options);
});
}
/*
* render loading first
* @params cb:Function
* @return
*/
}, {
key: 'renderLoading',
value: function renderLoading(cb) {
var _this2 = this;
this.state.loading = true;
loadImageAsync({
src: this.loading
}, function (data) {
_this2.render('loading', false);
_this2.state.loading = false;
cb();
}, function () {
// handler `loading image` load failed
cb();
_this2.state.loading = false;
if (!_this2.options.silent) console.warn('VueLazyload log: load failed with loading image(' + _this2.loading + ')');
});
}
/*
* try load image and render it
* @return
*/
}, {
key: 'load',
value: function load() {
var _this3 = this;
var onFinish = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 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(function () {
_this3.attempt++;
_this3.options.adapter['beforeLoad'] && _this3.options.adapter['beforeLoad'](_this3, _this3.options);
_this3.record('loadStart');
loadImageAsync({
src: _this3.src
}, function (data) {
_this3.naturalHeight = data.naturalHeight;
_this3.naturalWidth = data.naturalWidth;
_this3.state.loaded = true;
_this3.state.error = false;
_this3.record('loadEnd');
_this3.render('loaded', false);
_this3.state.rendered = true;
_this3._imageCache.add(_this3.src);
onFinish();
}, function (err) {
!_this3.options.silent && console.error(err);
_this3.state.error = true;
_this3.state.loaded = false;
_this3.render('error', false);
});
});
}
/*
* render image
* @param {String} state to render // ['loading', 'src', 'error']
* @param {String} is form cache
* @return
*/
}, {
key: 'render',
value: function render(state, cache) {
this.elRenderer(this, state, cache);
}
/*
* output performance data
* @return {Object} performance data
*/
}, {
key: 'performance',
value: function performance() {
var state = 'loading';
var 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: state,
time: time
};
}
/*
* $destroy
* @return
*/
}, {
key: '$destroy',
value: function $destroy() {
this.el = null;
this.src = null;
this.error = null;
this.loading = null;
this.bindType = null;
this.attempt = 0;
}
}]);
return ReactiveListener;
}();
var DEFAULT_URL = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
var DEFAULT_EVENTS = ['scroll', 'wheel', 'mousewheel', 'resize', 'animationend', 'transitionend', 'touchmove'];
var DEFAULT_OBSERVER_OPTIONS = {
rootMargin: '0px',
threshold: 0
};
var Lazy = function (Vue) {
return function () {
function Lazy(_ref) {
var preLoad = _ref.preLoad,
error = _ref.error,
throttleWait = _ref.throttleWait,
preLoadTop = _ref.preLoadTop,
dispatchEvent = _ref.dispatchEvent,
loading = _ref.loading,
attempt = _ref.attempt,
_ref$silent = _ref.silent,
silent = _ref$silent === undefined ? true : _ref$silent,
scale = _ref.scale,
listenEvents = _ref.listenEvents,
hasbind = _ref.hasbind,
filter = _ref.filter,
adapter = _ref.adapter,
observer = _ref.observer,
observerOptions = _ref.observerOptions;
classCallCheck(this, Lazy);
this.version = '1.3.3';
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
*/
createClass(Lazy, [{
key: 'config',
value: function config() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
assignDeep(this.options, options);
}
/**
* output listener's load performance
* @return {Array}
*/
}, {
key: 'performance',
value: function performance() {
var list = [];
this.ListenerQueue.map(function (item) {
list.push(item.performance());
});
return list;
}
/*
* add lazy component to queue
* @param {Vue} vm lazy component instance
* @return
*/
}, {
key: 'addLazyBox',
value: function 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
*/
}, {
key: 'add',
value: function add(el, binding, vnode) {
var _this = this;
if (some(this.ListenerQueue, function (item) {
return item.el === el;
})) {
this.update(el, binding);
return Vue.nextTick(this.lazyLoadHandler);
}
var _valueFormatter2 = this._valueFormatter(binding.value),
src = _valueFormatter2.src,
loading = _valueFormatter2.loading,
error = _valueFormatter2.error;
Vue.nextTick(function () {
src = getBestSelectionFromSrcset(el, _this.options.scale) || src;
_this._observer && _this._observer.observe(el);
var container = Object.keys(binding.modifiers)[0];
var $parent = void 0;
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);
}
var newListener = new ReactiveListener({
bindType: binding.arg,
$parent: $parent,
el: el,
loading: loading,
error: error,
src: src,
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(function () {
return _this.lazyLoadHandler();
});
});
}
/**
* update image src
* @param {DOM} el
* @param {object} vue directive binding
* @return
*/
}, {
key: 'update',
value: function update(el, binding, vnode) {
var _this2 = this;
var _valueFormatter3 = this._valueFormatter(binding.value),
src = _valueFormatter3.src,
loading = _valueFormatter3.loading,
error = _valueFormatter3.error;
src = getBestSelectionFromSrcset(el, this.options.scale) || src;
var exist = find(this.ListenerQueue, function (item) {
return item.el === el;
});
if (!exist) {
this.add(el, binding, vnode);
} else {
exist.update({
src: src,
loading: loading,
error: error
});
}
if (this._observer) {
this._observer.unobserve(el);
this._observer.observe(el);
}
this.lazyLoadHandler();
Vue.nextTick(function () {
return _this2.lazyLoadHandler();
});
}
/**
* remove listener form list
* @param {DOM} el
* @return
*/
}, {
key: 'remove',
value: function remove$$1(el) {
if (!el) return;
this._observer && this._observer.unobserve(el);
var existItem = find(this.ListenerQueue, function (item) {
return 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
*/
}, {
key: 'removeComponent',
value: function 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);
}
}, {
key: 'setMode',
value: function setMode(mode) {
var _this3 = this;
if (!hasIntersectionObserver && mode === modeType.observer) {
mode = modeType.event;
}
this.mode = mode; // event or observer
if (mode === modeType.event) {
if (this._observer) {
this.ListenerQueue.forEach(function (listener) {
_this3._observer.unobserve(listener.el);
});
this._observer = null;
}
this.TargetQueue.forEach(function (target) {
_this3._initListen(target.el, true);
});
} else {
this.TargetQueue.forEach(function (target) {
_this3._initListen(target.el, false);
});
this._initIntersectionObserver();
}
}
/*
*** Private functions ***
*/
/*
* add listener target
* @param {DOM} el listener target
* @return
*/
}, {
key: '_addListenerTarget',
value: function _addListenerTarget(el) {
if (!el) return;
var target = find(this.TargetQueue, function (target) {
return 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
*/
}, {
key: '_removeListenerTarget',
value: function _removeListenerTarget(el) {
var _this4 = this;
this.TargetQueue.forEach(function (target, index) {
if (target.el === el) {
target.childrenCount--;
if (!target.childrenCount) {
_this4._initListen(target.el, false);
_this4.TargetQueue.splice(index, 1);
target = null;
}
}
});
}
/*
* add or remove eventlistener
* @param {DOM} el DOM or Window
* @param {boolean} start flag
* @return
*/
}, {
key: '_initListen',
value: function _initListen(el, start) {
var _this5 = this;
this.options.ListenEvents.forEach(function (evt) {
return _[start ? 'on' : 'off'](el, evt, _this5.lazyLoadHandler);
});
}
}, {
key: '_initEvent',
value: function _initEvent() {
var _this6 = this;
this.Event = {
listeners: {
loading: [],
loaded: [],
error: []
}
};
this.$on = function (event, func) {
if (!_this6.Event.listeners[event]) _this6.Event.listeners[event] = [];
_this6.Event.listeners[event].push(func);
};
this.$once = function (event, func) {
var vm = _this6;
function on() {
vm.$off(event, on);
func.apply(vm, arguments);
}
_this6.$on(event, on);
};
this.$off = function (event, func) {
if (!func) {
if (!_this6.Event.listeners[event]) return;
_this6.Event.listeners[event].length = 0;
return;
}
remove(_this6.Event.listeners[event], func);
};
this.$emit = function (event, context, inCache) {
if (!_this6.Event.listeners[event]) return;
_this6.Event.listeners[event].forEach(function (func) {
return func(context, inCache);
});
};
}
/**
* find nodes which in viewport and trigger load
* @return
*/
}, {
key: '_lazyLoadHandler',
value: function _lazyLoadHandler() {
var _this7 = this;
var freeList = [];
this.ListenerQueue.forEach(function (listener, index) {
if (!listener.el || !listener.el.parentNode) {
freeList.push(listener);
}
var catIn = listener.checkInView();
if (!catIn) return;
listener.load();
});
freeList.forEach(function (item) {
remove(_this7.ListenerQueue, item);
item.$destroy();
});
}
/**
* init IntersectionObserver
* set mode to observer
* @return
*/
}, {
key: '_initIntersectionObserver',
value: function _initIntersectionObserver() {
var _this8 = this;
if (!hasIntersectionObserver) return;
this._observer = new IntersectionObserver(this._observerHandler.bind(this), this.options.observerOptions);
if (this.ListenerQueue.length) {
this.ListenerQueue.forEach(function (listener) {
_this8._observer.observe(listener.el);
});
}
}
/**
* init IntersectionObserver
* @return
*/
}, {
key: '_observerHandler',
value: function _observerHandler(entries, observer) {
var _this9 = this;
entries.forEach(function (entry) {
if (entry.isIntersecting) {
_this9.ListenerQueue.forEach(function (listener) {
if (listener.el === entry.target) {
if (listener.state.loaded) return _this9._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
*/
}, {
key: '_elRenderer',
value: function _elRenderer(listener, state, cache) {
if (!listener.el) return;
var el = listener.el,
bindType = listener.bindType;
var src = void 0;
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) {
var 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
*/
}, {
key: '_valueFormatter',
value: function _valueFormatter(value) {
var src = value;
var loading = this.options.loading;
var error = this.options.error;
// value is object
if (isObject(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: src,
loading: loading,
error: error
};
}
}]);
return Lazy;
}();
};
var LazyComponent = (function (lazy) {
return {
props: {
tag: {
type: String,
default: 'div'
}
},
render: function render(h) {
if (this.show === false) {
return h(this.tag);
}
return h(this.tag, null, this.$slots.default);
},
data: function data() {
return {
el: null,
state: {
loaded: false
},
rect: {},
show: false
};
},
mounted: function mounted() {
this.el = this.$el;
lazy.addLazyBox(this);
lazy.lazyLoadHandler();
},
beforeDestroy: function beforeDestroy() {
lazy.removeComponent(this);
},
methods: {
getRect: function getRect() {
this.rect = this.$el.getBoundingClientRect();
},
checkInView: function 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: function load() {
this.show = true;
this.state.loaded = true;
this.$emit('show', this);
},
destroy: function destroy() {
return this.$destroy;
}
}
};
});
var LazyContainerMananger = function () {
function LazyContainerMananger(_ref) {
var lazy = _ref.lazy;
classCallCheck(this, LazyContainerMananger);
this.lazy = lazy;
lazy.lazyContainerMananger = this;
this._queue = [];
}
createClass(LazyContainerMananger, [{
key: 'bind',
value: function bind(el, binding, vnode) {
var container = new LazyContainer$1({ el: el, binding: binding, vnode: vnode, lazy: this.lazy });
this._queue.push(container);
}
}, {
key: 'update',
value: function update(el, binding, vnode) {
var container = find(this._queue, function (item) {
return item.el === el;
});
if (!container) return;
container.update({ el: el, binding: binding, vnode: vnode });
}
}, {
key: 'unbind',
value: function unbind(el, binding, vnode) {
var container = find(this._queue, function (item) {
return item.el === el;
});
if (!container) return;
container.clear();
remove(this._queue, container);
}
}]);
return LazyContainerMananger;
}();
var defaultOptions = {
selector: 'img'
};
var LazyContainer$1 = function () {
function LazyContainer(_ref2) {
var el = _ref2.el,
binding = _ref2.binding,
vnode = _ref2.vnode,
lazy = _ref2.lazy;
classCallCheck(this, LazyContainer);
this.el = null;
this.vnode = vnode;
this.binding = binding;
this.options = {};
this.lazy = lazy;
this._queue = [];
this.update({ el: el, binding: binding });
}
createClass(LazyContainer, [{
key: 'update',
value: function update(_ref3) {
var _this = this;
var el = _ref3.el,
binding = _ref3.binding;
this.el = el;
this.options = assignDeep({}, defaultOptions, binding.value);
var imgs = this.getImgs();
imgs.forEach(function (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);
});
}
}, {
key: 'getImgs',
value: function getImgs() {
return ArrayFrom(this.el.querySelectorAll(this.options.selector));
}
}, {
key: 'clear',
value: function clear() {
var _this2 = this;
var imgs = this.getImgs();
imgs.forEach(function (el) {
return _this2.lazy.remove(el);
});
this.vnode = null;
this.binding = null;
this.lazy = null;
}
}]);
return LazyContainer;
}();
var LazyImage = (function (lazyManager) {
return {
props: {
src: [String, Object],
tag: {
type: String,
default: 'img'
}
},
render: function render(h) {
return h(this.tag, {
attrs: {
src: this.renderSrc
}
}, this.$slots.default);
},
data: function data() {
return {
el: null,
options: {
src: '',
error: '',
loading: '',
attempt: lazyManager.options.attempt
},
state: {
loaded: false,
error: false,
attempt: 0
},
rect: {},
renderSrc: ''
};
},
watch: {
src: function src() {
this.init();
lazyManager.addLazyBox(this);
lazyManager.lazyLoadHandler();
}
},
created: function created() {
this.init();
this.renderSrc = this.options.loading;
},
mounted: function mounted() {
this.el = this.$el;
lazyManager.addLazyBox(this);
lazyManager.lazyLoadHandler();
},
beforeDestroy: function beforeDestroy() {
lazyManager.removeComponent(this);
},
methods: {
init: function init() {
var _lazyManager$_valueFo = lazyManager._valueFormatter(this.src),
src = _lazyManager$_valueFo.src,
loading = _lazyManager$_valueFo.loading,
error = _lazyManager$_valueFo.error;
this.state.loaded = false;
this.options.src = src;
this.options.error = error;
this.options.loading = loading;
this.renderSrc = this.options.loading;
},
getRect: function getRect() {
this.rect = this.$el.getBoundingClientRect();
},
checkInView: function 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: function load() {
var _this = this;
var onFinish = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 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;
}
var src = this.options.src;
loadImageAsync({ src: src }, function (_ref) {
var src = _ref.src;
_this.renderSrc = src;
_this.state.loaded = true;
}, function (e) {
_this.state.attempt++;
_this.renderSrc = _this.options.error;
_this.state.error = true;
});
}
}
};
});
var index = {
/*
* install function
* @param {Vue} Vue
* @param {object} options lazyload options
*/
install: function install(Vue) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var LazyClass = Lazy(Vue);
var lazy = new LazyClass(options);
var lazyContainer = new LazyContainerMananger({ lazy: lazy });
var 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: function 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: function unbind() {
lazy.remove(this.el);
}
});
Vue.directive('lazy-container', {
update: function update(newValue, oldValue) {
lazyContainer.update(this.el, {
modifiers: this.modifiers || {},
arg: this.arg,
value: newValue,
oldValue: oldValue
}, {
context: this.vm
});
},
unbind: function unbind() {
lazyContainer.unbind(this.el);
}
});
}
}
};
export default index;