v-lazyload
const getScrollParent = (el) => {
const parent = el.parentNode
while(parent){
if(/scroll|auto/.test(getComputedStyle(parent).overflow)){
return parent
}
parent = parent.parentNode
}
return parent
}
const loadImgAsync = (src, resolve, reject) => {
const img = new Image()
img.src = src
img.onload = resolve
img.onerror = reject
}
class ReactiveListener {
constructor({el, src, options, elRender}){
this.el = el
this.src = src
this.options = options
this.elRender = elRender
this.state = {
loading: false,
error: false
}
}
checkInView() {
const { top } = this.el.getBoundingClientRect()
return top < window.innerHeight * (this.options.preload || 1.3)
}
load(){
this.elRender(this, 'loading')
loadImgAsync(this.src, () => {
this.state.loading = true
this.elRender(this, 'finish')
}, () => {
this.state.error = true
this.elRender(this, 'error')
})
}
}
const lazy = (Vue) => {
return class {
constructor(options) {
this.options = options
this.bindHandle = false
this.listenerQueue = []
}
handleLazyload(){
this.listenerQueue.forEach(listener => {
let canIn = listener.checkInView()
if(canIn){
listener.load()
}
})
}
add(el, bindings, vnode){
Vue.$nextTick(() => {
let scrollParent = getScrollParent(el)
if(scrollParent && !this.bindHandle){
this.bindHandle = true
scrollParent.addEventListener('scroll', this.handleLazyload.bind(this))
}
const listener = new ReactiveListener({
el,
src: bindings.value,
options:this.options,
elRender: this.elRender.bind(this)
})
this.listenerQueue.push(listener)
this.handleLazyload()
})
}
elRender(listener, state){
let el = listener.el
let src = ''
switch (state) {
case 'loading':
src = listener.options.loading
break;
case 'error':
src = listener.options.error
break;
default:
src = listener.src
break;
}
el.setAttribute('src', src)
}
}
}
const VueLazyload = {
install(Vue, options){
let LazyClass = lazy(Vue)
let lazy = new LazyClass(options)
Vue.directive('lazyload', {
bind: lazy.add.bind(lazy)
})
}
}