import {
|
loadImageAsync,
|
ObjectKeys,
|
noop
|
} from './util'
|
|
let imageCache = {}
|
|
// el: {
|
// state,
|
// src,
|
// error,
|
// loading
|
// }
|
|
export default class ReactiveListener {
|
constructor ({ el, src, error, loading, bindType, $parent, options, elRenderer }) {
|
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.performanceData = {
|
init: Date.now(),
|
loadStart: 0,
|
loadEnd: 0
|
}
|
|
this.filter()
|
this.initState()
|
this.render('loading', false)
|
}
|
|
/*
|
* init listener state
|
* @return
|
*/
|
initState () {
|
this.el.dataset.src = this.src
|
this.state = {
|
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) {
|
loadImageAsync({
|
src: this.loading
|
}, data => {
|
this.render('loading', false)
|
cb()
|
}, () => {
|
// handler `loading image` load failed
|
cb()
|
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.loaded || imageCache[this.src]) {
|
this.state.loaded = true
|
onFinish()
|
return this.render('loaded', true)
|
}
|
|
this.renderLoading(() => {
|
this.attempt++
|
|
this.record('loadStart')
|
|
loadImageAsync({
|
src: this.src
|
}, data => {
|
this.naturalHeight = data.naturalHeight
|
this.naturalWidth = data.naturalWidth
|
this.state.loaded = true
|
this.state.error = false
|
this.record('loadEnd')
|
this.render('loaded', false)
|
imageCache[this.src] = 1
|
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
|
}
|
}
|