前端入门之(vue图片加载框架一)
前言: 之前做android的时候,会接触各种图片加载框架,也自己封装过,封装网络框架目的无非就是为了提高图片的复用性、减少内存消耗、监听图片的加载过程等等.换成web前端其实是一样的操作,好啦! 说了那么多我们来简单的实现一个图片加载框架,小伙伴跟紧了哦!!!
因为一直在做vue,所以我就以vue为基础来开发我们的图片加载框架了,我们新见一个vue项目,然后运行(我就以之前的vuex的demo为例子了,感兴趣的童鞋可以看看我之前写的vuex的几篇文章).
<template><div class="opt-container"><img :src="data:images[0]"></div>
</template><script>export default {name: 'Lazy',data() {return {images: ['https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1533137283186&di=c136f7387cfe2b79161f2f93bff6cb96&imgtype=0&src=http%3A%2F%2Fpic1.cxtuku.com%2F00%2F09%2F65%2Fb3468db29cb1.jpg','https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1533137283186&di=de941561df3b6fd53b2df9bfd6c0b187&imgtype=0&src=http%3A%2F%2Fpic43.photophoto.cn%2F20170413%2F0008118236659168_b.jpg','https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1533137283185&di=aff7e8aa60813f6e36ebc6f6a961255c&imgtype=0&src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F01d60f57e8a07d0000018c1bfa2564.JPG%403000w_1l_2o_100sh.jpg',]}}}
</script><!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>.opt-container {font-size: 0px;}
</style>
可以看到,很简单! 我们就是放了一个img标签.然后加载了一张图片:
现在有一个这样的需求,因为我们的图片比较大,所以当图片正在加载的时候,我们显示loading图片,然后当我们图片加载失败的时候,我们显示一个失败的默认图,当我们图片正在加载成功的时候,我们再显示.
我们来试一下哈~~
我们定义一个方法,叫loadImageAsync:
loadImageAsync(item, resolve, reject) {let image = new Image();image.src = item.src;image.onload = function () {resolve({naturalHeight: image.naturalHeight,naturalWidth: image.naturalWidth,src: image.src});};image.onerror = function (e) {reject(e)};}}
代码很简单,我就不解释了~~ ,接下来是在我们的created的时候调用此方法:
created() {let item = {src: this.images[0]};this.loadImageAsync(item,(response)=>{console.log('图片加载成功');console.log('图片的宽度为:'+response.naturalWidth);console.log('图片的高度为:'+response.naturalHeight);},(error)=>{console.log('图片加载失败');});}
我们重写运行代码看log:
[HMR] Waiting for update signal from WDS...
Lazy.vue?2392:40 图片加载成功
Lazy.vue?2392:41 图片的宽度为:600
Lazy.vue?2392:42 图片的高度为:398
我们可以看到,log里面打印出来了日志,然后把图片的宽高都打印出来了,我们试着把图片链接写错试一下:
created() {//我们把链接写错let item = {src: 11111+this.images[0]};this.loadImageAsync(item,(response)=>{console.log('图片加载成功');console.log('图片的宽度为:'+response.naturalWidth);console.log('图片的高度为:'+response.naturalHeight);},(error)=>{console.log('图片加载失败');});}
重新运行代码:
[HMR] Waiting for update signal from WDS...
Lazy.vue?2392:44 图片加载失败
可以看到,图片加载失败了~~
好啦,有了图片的监听,我们就可以操作了,我们首先准备两张图片,一张为loading(加载中图片),一张为erro(加载失败的图片).
然后我们动态的给img标签设置上src:
<template><div class="opt-container"><img :src="currSrc"></div>
</template><script>const errorImg = require('./error.png');const loadingImg = require('./loading.png');export default {name: 'Lazy',data() {return {images: ['https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1533137283186&di=c136f7387cfe2b79161f2f93bff6cb96&imgtype=0&src=http%3A%2F%2Fpic1.cxtuku.com%2F00%2F09%2F65%2Fb3468db29cb1.jpg','https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1533137283186&di=de941561df3b6fd53b2df9bfd6c0b187&imgtype=0&src=http%3A%2F%2Fpic43.photophoto.cn%2F20170413%2F0008118236659168_b.jpg','https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1533137283185&di=aff7e8aa60813f6e36ebc6f6a961255c&imgtype=0&src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F01d60f57e8a07d0000018c1bfa2564.JPG%403000w_1l_2o_100sh.jpg',],currSrc: loadingImg //默认为加载中状态}},methods: {loadImageAsync(item, resolve, reject) {let image = new Image();image.src = item.src;image.onload = function () {resolve({naturalHeight: image.naturalHeight,naturalWidth: image.naturalWidth,src: image.src});};image.onerror = function (e) {reject(e)};}},created() {let item = {src: this.images[0]};this.loadImageAsync(item, (response) => {console.log('图片加载成功');console.log('图片的宽度为:' + response.naturalWidth);console.log('图片的高度为:' + response.naturalHeight);//当图片加载成功的时候,把图片的src换成目标地址this.currSrc = response.src;}, (error) => {console.log('图片加载失败');//当图片加载失败的时候,把图片的src换成失败的图片this.currSrc = errorImg;});}}
</script><!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>.opt-container {font-size: 0px;}
</style>
代码都有注释,小伙伴应该看得懂哈~~我们运行一下:
太快了,录屏看不出loading的效果,我们把图片链接改错试试:
let item = {src: 1111+this.images[0]};
可以看到,我们的图片加载失败就显示了一张失败的默认图,好啦! 到这里我们的简单的需求算是实现了,这时,有小伙伴就要说了,你这也太麻烦了,我只有一张图片还好,既然是框架,那就得是针对整个工程, 是的!! 我们就封装一下我们的代码,最后实现的时候,我们只需要这样写就好了:
<template><div class="opt-container"><img v-lazy="{src:images[0]}"></div>
</template>
我们通过指令的形式来加载我们的图片,然后在指令中去切换图片状态,没看过指令的童鞋自己去看官网哈https://cn.vuejs.org/v2/guide/custom-directive.html
好啦,我们开动啦~~~
第一步:
我们创建一个叫lazy的文件夹,然后返回一个带install方法的对象:
/*** @author YASIN* @version [React-Native Ocj V01, 2018/8/1]* @date 17/2/23* @description index*/
export default {install(Vue, options = {}) {console.log('install be called!!');}
}
然后我们在项目的main.js用一下我们的这个插件:
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'import LazyImage from './lazy'Vue.config.productionTip = false
Vue.use(LazyImage)
/* eslint-disable no-new */
new Vue({el: '#app',router,store,components: {App},template: '<App/>'
})
我们运行代码:
[HMR] Waiting for update signal from WDS...
index.js?bd6a:9 install be called!!
可以看到,我们的install方法被调用了~~
第二步:
定义lazy指令
/*** @author YASIN* @version [React-Native Ocj V01, 2018/8/1]* @date 17/2/23* @description index*/
export default {install(Vue, options = {}) {Vue.directive('lazy', {bind: function (el, binding, vnode) {let s = JSON.stringify;let result = ('name: ' + s(binding.name) + '\n' +'value: ' + s(binding.value) + '\n' +'expression: ' + s(binding.expression) + '\n' +'argument: ' + s(binding.arg) + '\n' +'modifiers: ' + s(binding.modifiers) + '\n' +'vnode keys: ' + Object.keys(vnode).join(', '))console.log('bind', result);},update: function () {console.log('update');},componentUpdated: function () {console.log('componentUpdated');},unbind: function () {console.log('unbind');},})}
}
我们运行代码:
[HMR] Waiting for update signal from WDS...
index.js?bd6a:20 bind name: "lazy"
value: {"src":"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1533137283186&di=c136f7387cfe2b79161f2f93bff6cb96&imgtype=0&src=http%3A%2F%2Fpic1.cxtuku.com%2F00%2F09%2F65%2Fb3468db29cb1.jpg"}
expression: "{src:images[0]}"
argument: undefined
modifiers: {}
vnode keys: tag, data, children, text, elm, ns, context, fnContext, fnOptions, fnScopeId, key, componentOptions, componentInstance, parent, raw, isStatic, isRootInsert, isComment, isCloned, isOnce, asyncFactory, asyncMeta, isAsyncPlaceholder
Lazy.vue?2392:50 图片加载失败
log有点多哈,不过结合我们的指令:
<img v-lazy="{src:images[0]}">
我们可以发现,我们可以从value中获取我们的src~~
好啦,定义好指令后,我们继续创建一个叫lazy的类,把一些基本的操作放在这个类中.
第三步:
实现lazy类:
/*** @author YASIN* @version [React-Native Ocj V01, 2018/8/1]* @date 17/2/23* @description LazyDelegate*/
const DEFAULT_ERRO_URL = require('../components/error.png');
const DEFAULT_LOADING_URL = require('../components/loading.png');
export default function (Vue) {return class Lazy {constructor({error, throttleWait, loading, attempt}) {this.options = {throttleWait: throttleWait || 200,//截流时间error: error || DEFAULT_ERRO_URL,//默认失败图片loading: loading || DEFAULT_LOADING_URL,//默认成功图片attempt: attempt || 3 //重试次数}}add(el, binding, vnode) {console.log('add');}update(el, binding) {console.log('update');}remove(el) {console.log('remove');}}
}
然后把我们的指令的构造函数跟我们的lazy类的方法绑定起来:
/*** @author YASIN* @version [React-Native Ocj V01, 2018/8/1]* @date 17/2/23* @description index*/
import lazyDelegate from './LazyDelegate';export default {install(Vue, options = {}) {let LazyClass = lazyDelegate(Vue);let lazy = new LazyClass(options);Vue.directive('lazy', {bind: lazy.add.bind(lazy),update: lazy.update.bind(lazy),componentUpdated: function () {console.log('componentUpdated');},unbind: lazy.remove.bind(lazy),})}
}
我们找到lazy的add方法:
/*** 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。* @param el 指令所绑定的元素,可以用来直接操作 DOM 。* @param binding* @param vnode*/add(el, binding, vnode) {console.log('add');}
我们首先获取我们指令中的src,然后获取我们定义的error跟loading:
_valueFormatter(value) {let src = value;let loading = this.options.loading;let error = this.options.error;// 如果value是一个object类型的时候if (value !== null && typeof value === 'object') {src = value.src;loading = value.loading || loading;error = value.error || error;}return {src,loading,error}}
因为我们的框架只有一个,但是我们的标签有很多个,所以我们针对每一个标签创建一个LazyListener加载工具类.然后在lazy中用一个数组统一的保存起来.
我们创建一个叫LazyListener的类:
/*** @author YASIN* @version [React-Native Ocj V01, 2018/8/1]* @date 17/2/23* @description listener*/
export default class LazyListener {constructor({el, src, error, loading, options, elRenderer}) {this.el = elthis.src = srcthis.error = errorthis.loading = loadingthis.naturalHeight = 0this.naturalWidth = 0this.options = options//组件状态渲染方法this.elRenderer = elRenderer//初始化组件状态this.initState()}/*** 初始化组件状态*/initState() {this.state = {error: false,loaded: false,rendered: false}}}
然后在我们的lazy的add方法中创建一个listerner:
add(el, binding, vnode) {let {src, loading, error} = this._valueFormatter(binding.value)Vue.nextTick(()=>{const newListener = new ReactiveListener({el,loading,error,src,elRenderer: this._elRenderer.bind(this),})})}
然后把创建的listerner保存在lazy的数组中:
return class Lazy {constructor({error, throttleWait, loading, attempt}) {//存放每一个元素的Listener加载类this.ListenerQueue = []......}
/*** 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。* @param el 指令所绑定的元素,可以用来直接操作 DOM 。* @param binding* @param vnode*/add(el, binding, vnode) {...Vue.nextTick(() => {const newListener = new ReactiveListener({el,loading,error,src,elRenderer: this._elRenderer.bind(this),})//将加载代理类加入到lazy的代理数组中this.ListenerQueue.push(newListener)})}
好啦,我们的全局lazy(经理)类创建好了,然后我们的经纪人(listener)也创建好了,我们接下来就是让经理通知经纪人干活了, 经理是一个人,经纪人有很多,所以我们把消息发到群里就可以了,我们创建一个群对话方法叫_lazyLoadHandler:
/*** 通知所有的listener该干活了* @private*/_lazyLoadHandler () {//找出哪些是已经完成工作了的const freeList = []this.ListenerQueue.forEach((listener, index) => {//状态是非错误的并且是已完成的叫完成工作的人if (!listener.state.error && listener.state.loaded) {return freeList.push(listener)}//通知未完成工作的人干活了listener.load()})//把完成工作的listener剔除freeList.forEach(vm => remove(this.ListenerQueue, vm))}
然后我们去listener中定义一个叫load的方法:
/*** 加载图片的方法* @param onFinish 完成回调*/load(onFinish) {console.log('load------>');}
因为我们的_lazyLoadHandler函数可能会被频繁的调用,这样就会阻塞js线程,体验不太好,所以我们给_lazyLoadHandler方法封装一下,加一个截流函数:
return class Lazy {constructor({error, throttleWait, loading, attempt}) {//存放每一个元素的Listener加载类this.ListenerQueue = []this.options = {throttleWait: throttleWait || 200,//截流时间...}this.lazyLoadHandler = throttle(this._lazyLoadHandler.bind(this), this.options.throttleWait)}
function throttle (action, delay) {let timeout = nulllet lastRun = 0return function () {if (timeout) {return}let elapsed = Date.now() - lastRunlet context = thislet args = argumentslet runCallback = function () {lastRun = Date.now()timeout = falseaction.apply(context, args)}if (elapsed >= delay) {runCallback()} else {timeout = setTimeout(runCallback, delay)}}
}
好啦,当我们一切准备就绪的时候,我们在我们的add方法中调用我们的lazyLoadHandler方法通知listener去加载图片:
/*** 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。* @param el 指令所绑定的元素,可以用来直接操作 DOM 。* @param binding* @param vnode*/add(el, binding, vnode) {let {src, loading, error} = this._valueFormatter(binding.value)Vue.nextTick(() => {const newListener = new ReactiveListener({el,loading,error,src,// elRenderer: this._elRenderer.bind(this),})this.ListenerQueue.push(newListener)//通知listener去加载图片this.lazyLoadHandler()//通知listener去加载图片Vue.nextTick(() => this.lazyLoadHandler())})}
我们代理类的全部代码:
/*** @author YASIN* @version [React-Native Ocj V01, 2018/8/1]* @date 17/2/23* @description LazyDelegate*/
import LazyListener from './listener';
const DEFAULT_ERRO_URL = require('../components/error.png');
const DEFAULT_LOADING_URL = require('../components/loading.png');
export default function (Vue) {return class Lazy {constructor({error, throttleWait, loading, attempt}) {//存放每一个元素的Listener加载类this.ListenerQueue = []this.options = {throttleWait: throttleWait || 200,//截流时间error: error || DEFAULT_ERRO_URL,//默认失败图片loading: loading || DEFAULT_LOADING_URL,//默认成功图片attempt: attempt || 3 //重试次数}this.lazyLoadHandler = throttle(this._lazyLoadHandler.bind(this), this.options.throttleWait)}/*** 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。* @param el 指令所绑定的元素,可以用来直接操作 DOM 。* @param binding* @param vnode*/add(el, binding, vnode) {let {src, loading, error} = this._valueFormatter(binding.value)Vue.nextTick(() => {const newListener = new LazyListener({el,loading,error,src,// elRenderer: this._elRenderer.bind(this),})this.ListenerQueue.push(newListener)Vue.nextTick(() => this.lazyLoadHandler())})}update(el, binding) {console.log('update');}remove(el) {console.log('remove');}/*** 通知所有的listener该干活了* @private*/_lazyLoadHandler () {//找出哪些是已经完成工作了的const freeList = []this.ListenerQueue.forEach((listener, index) => {if (!listener.state.error && listener.state.loaded) {return freeList.push(listener)}listener.load()})//把完成工作的listener剔除freeList.forEach(vm => remove(this.ListenerQueue, vm))}_valueFormatter(value) {let src = value;let loading = this.options.loading;let error = this.options.error;// 如果value是一个object类型的时候if (value !== null && typeof value === 'object') {src = value.src;loading = value.loading || loading;error = value.error || error;}return {src,loading,error}}}
}
function throttle (action, delay) {let timeout = nulllet lastRun = 0return function () {if (timeout) {return}let elapsed = Date.now() - lastRunlet context = thislet args = argumentslet runCallback = function () {lastRun = Date.now()timeout = falseaction.apply(context, args)}if (elapsed >= delay) {runCallback()} else {timeout = setTimeout(runCallback, delay)}}
}
我们listener的全部代码:
/*** @author YASIN* @version [React-Native Ocj V01, 2018/8/1]* @date 17/2/23* @description listener*/
export default class LazyListener {constructor({el, src, error, loading, options, elRenderer}) {this.el = elthis.src = srcthis.error = errorthis.loading = loadingthis.naturalHeight = 0this.naturalWidth = 0this.options = options//组件状态渲染方法this.elRenderer = elRenderer//初始化组件状态this.initState()}/*** 初始化组件状态*/initState() {this.state = {error: false,loaded: false,rendered: false}}/*** 加载图片的方法* @param onFinish 完成回调*/load(onFinish) {console.log('load------>');}
}
当我运行代码:
[HMR] Waiting for update signal from WDS...
listener.js?4bef:40 load------>
可以看到,我们的listener(经纪人)的load方法执行了~~
然后我们把我们一开始写的loadImageAsync方法搬进我们的listener中:
const loadImageAsync = (item, resolve, reject) => {let image = new Image()image.src = item.srcimage.onload = function () {resolve({naturalHeight: image.naturalHeight,naturalWidth: image.naturalWidth,src: image.src})}image.onerror = function (e) {reject(e)}
}
然后我们首先是渲染我们的loading:
/*** 加载图片的方法* @param onFinish 完成回调*/load(onFinish) {//如果重试的次数>我们设置的次数并且失败的时候我们直接不加载了if ((this.attempt > this.options.attempt - 1) && this.state.error) {onFinish && onFinish()return}//如果该组件已经加载完毕了直接结束if (this.state.loaded) {this.state.loaded = trueonFinish && onFinish()//渲染srcreturn this.render('loaded')}this.renderLoading(() => {this.attempt++loadImageAsync({src: this.src}, data => {this.naturalHeight = data.naturalHeightthis.naturalWidth = data.naturalWidththis.state.loaded = truethis.state.error = falsethis.render('loaded')onFinish && onFinish()}, err => {this.state.error = truethis.state.loaded = falsethis.render('error')})})}
/*** 渲染loading* @param cb 回调*/renderLoading(cb) {loadImageAsync({src: this.loading}, data => {this.render('loading')cb()}, () => {cb()})}
然后加载完了统一执行render方法:
/*** 根据状态渲染src* @param state*/render(state) {this.elRenderer(this, state)}
constructor({el, src, error, loading, options, elRenderer}) {..this.elRenderer = elRenderer//初始化组件状态this.initState()}
其中listener的elRenderer方法其实是经理(lazy)传过去的,所以我们在lazy类中统一定义一个elRenderer方法:
/*** 根据状态渲染src* @param listener 经纪人* @param state 状态* @private*/_elRenderer(listener, state) {if (!listener.el) returnconst {el} = listenerlet srcswitch (state) {case 'loading':src = listener.loadingbreakcase 'error':src = listener.errorbreakdefault:src = listener.srcbreak}//通过js方法给el设置上src属性if (el.getAttribute('src') !== src) {el.setAttribute('src', src)}el.setAttribute('lazy', state)}
然后在创建listener的时候传给listener(经纪人):
add(el, binding, vnode) {let {src, loading, error} = this._valueFormatter(binding.value)Vue.nextTick(() => {const newListener = new LazyListener({el,loading,error,src,options: this.options,elRenderer: this._elRenderer.bind(this),})this.ListenerQueue.push(newListener)Vue.nextTick(() => this.lazyLoadHandler())})}
好啦,我们经理lazy的全部代码:
/*** @author YASIN* @version [React-Native Ocj V01, 2018/8/1]* @date 17/2/23* @description LazyDelegate*/
import LazyListener from './listener';const DEFAULT_ERRO_URL = require('../components/error.png');
const DEFAULT_LOADING_URL = require('../components/loading.png');
export default function (Vue) {return class Lazy {constructor({error, throttleWait, loading, attempt}) {//存放每一个元素的Listener加载类this.ListenerQueue = []this.options = {throttleWait: throttleWait || 200,//截流时间error: error || DEFAULT_ERRO_URL,//默认失败图片loading: loading || DEFAULT_LOADING_URL,//默认成功图片attempt: attempt || 3 //重试次数}this.lazyLoadHandler = throttle(this._lazyLoadHandler.bind(this), this.options.throttleWait)}/*** 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。* @param el 指令所绑定的元素,可以用来直接操作 DOM 。* @param binding* @param vnode*/add(el, binding, vnode) {let {src, loading, error} = this._valueFormatter(binding.value)Vue.nextTick(() => {const newListener = new LazyListener({el,loading,error,src,options: this.options,elRenderer: this._elRenderer.bind(this),})this.ListenerQueue.push(newListener)Vue.nextTick(() => this.lazyLoadHandler())})}update(el, binding) {console.log('update');}remove(el) {console.log('remove');}/*** 通知所有的listener该干活了* @private*/_lazyLoadHandler() {//找出哪些是已经完成工作了的const freeList = []this.ListenerQueue.forEach((listener, index) => {if (!listener.state.error && listener.state.loaded) {return freeList.push(listener)}listener.load()})//把完成工作的listener剔除freeList.forEach(vm => remove(this.ListenerQueue, vm))}/*** 根据状态渲染src* @param listener 经纪人* @param state 状态* @private*/_elRenderer(listener, state) {if (!listener.el) returnconst {el} = listenerlet srcswitch (state) {case 'loading':src = listener.loadingbreakcase 'error':src = listener.errorbreakdefault:src = listener.srcbreak}//通过js方法给el设置上src属性if (el.getAttribute('src') !== src) {el.setAttribute('src', src)}el.setAttribute('lazy', state)}_valueFormatter(value) {let src = value;let loading = this.options.loading;let error = this.options.error;// 如果value是一个object类型的时候if (value !== null && typeof value === 'object') {src = value.src;loading = value.loading || loading;error = value.error || error;}return {src,loading,error}}}
}function throttle(action, delay) {let timeout = nulllet lastRun = 0return function () {if (timeout) {return}let elapsed = Date.now() - lastRunlet context = thislet args = argumentslet runCallback = function () {lastRun = Date.now()timeout = falseaction.apply(context, args)}if (elapsed >= delay) {runCallback()} else {timeout = setTimeout(runCallback, delay)}}
}
我们经纪人(listener)的全部代码:
/*** @author YASIN* @version [React-Native Ocj V01, 2018/8/1]* @date 17/2/23* @description listener*/
export default class LazyListener {constructor({el, src, error, loading, options, elRenderer}) {this.el = elthis.src = srcthis.error = errorthis.loading = loadingthis.attempt = 0 //重试次数this.naturalHeight = 0this.naturalWidth = 0this.options = options//组件状态渲染方法this.elRenderer = elRenderer//初始化组件状态this.initState()}/*** 初始化组件状态*/initState() {this.state = {error: false,loaded: false,rendered: false}}/*** 加载图片的方法* @param onFinish 完成回调*/load(onFinish) {//如果重试的次数>我们设置的次数并且失败的时候我们直接不加载了if ((this.attempt > this.options.attempt - 1) && this.state.error) {onFinish && onFinish()return}//如果该组件已经加载完毕了直接结束if (this.state.loaded) {this.state.loaded = trueonFinish && onFinish()//渲染srcreturn this.render('loaded')}this.renderLoading(() => {this.attempt++loadImageAsync({src: this.src}, data => {this.naturalHeight = data.naturalHeightthis.naturalWidth = data.naturalWidththis.state.loaded = truethis.state.error = falsethis.render('loaded')onFinish && onFinish()}, err => {this.state.error = truethis.state.loaded = falsethis.render('error')})})}/*** 渲染loading* @param cb 回调*/renderLoading(cb) {loadImageAsync({src: this.loading}, data => {this.render('loading')cb()}, () => {cb()})}/*** 根据状态渲染src* @param state*/render(state) {this.elRenderer(this, state)}
}
const loadImageAsync = (item, resolve, reject) => {let image = new Image()image.src = item.srcimage.onload = function () {resolve({naturalHeight: image.naturalHeight,naturalWidth: image.naturalWidth,src: image.src})}image.onerror = function (e) {reject(e)}
}
然后我们的测试类中的全部代码:
<template><div class="opt-container"><img v-lazy="{src:images[1]}"></div>
</template><script>export default {name: 'Lazy',data() {return {images: ['https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1533137283186&di=c136f7387cfe2b79161f2f93bff6cb96&imgtype=0&src=http%3A%2F%2Fpic1.cxtuku.com%2F00%2F09%2F65%2Fb3468db29cb1.jpg','https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1533137283186&di=de941561df3b6fd53b2df9bfd6c0b187&imgtype=0&src=http%3A%2F%2Fpic43.photophoto.cn%2F20170413%2F0008118236659168_b.jpg','https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1533137283185&di=aff7e8aa60813f6e36ebc6f6a961255c&imgtype=0&src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F01d60f57e8a07d0000018c1bfa2564.JPG%403000w_1l_2o_100sh.jpg',]}}}
</script><!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>.opt-container {font-size: 0px;}
</style>
运行代码:
我们写一个错误的图片链接试试:
<template><div class="opt-container"><img v-lazy="{src:111+images[1]}"></div>
</template>
好啦~~ 已经基本实现了我们的效果了~~篇幅有点长了,写得我都睡着了,细心的小伙伴可能会发现,代码长得好像一个叫vue-lazyload的框架,是的!! 我就是一点一点在解析它的源码,哈哈哈!!! 小伙伴不要失望哈,学习别人的东西不一定就是很丢丑的一件事情,别人牛逼干嘛不去学习呢??
好啦~ 先附上vue-lazyload框架的地址:
https://github.com/hilongjw/vue-lazyload
当然!! 我这个只是一个demo,小伙伴千万不要直接丢到项目中哦,要用的话直接去拖vue-lazyload的代码就好了.
这一节先结束了,下一节我将带大家一起实现(懒加载、缓存、监听等)未实现的功能,睡觉哒!!!!!! 欢迎入群,欢迎交流~~~~
前端入门之(vue图片加载框架一)相关推荐
- Android Glide图片加载框架(一)基本用法
文章目录 一.前言 二.简介 三.基本用法 第一步:调用 Glide.with() 方法创建加载图片的实例 第二步:调用 load() 方法指定待加载的图片资源 第三步:调用 into() 方法绑定显 ...
- Android图片加载框架最全解析(八),带你全面了解Glide 4的用法
本文转载自郭神的Glide分析系列:http://blog.csdn.net/guolin_blog/article/details/78582548 本文同步发表于我的微信公众号,扫一扫文章底部的二 ...
- Android图片加载框架 Glide 4 的用法
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/78582548 本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭 ...
- Fresco图片加载框架的介绍,相关开源库以及工具类的封装
Fresco图片加载框架的介绍,相关开源库以及工具类的封装 本文已授权微信公众号:鸿洋(hongyangAndroid)在微信公众号平台原创首发. 简介 Fresco 是Facebook开源的安卓上的 ...
- Fresco图片加载框架使用方法完全指南
简介 Fresco 是Facebook开源的安卓上的图片加载框架,也可以说是至今为止安卓上最强大的图片加载框架. 相对于其他几个图片加载框架,Fresco主要的优点在于更好的内存管理和更强大的功能,更 ...
- Android图片加载框架——Glide(Glide v4)
原文地址 Android图片加载框架--Glide(Glide v4) 前言 android中图片加载框架有很多,所有框架最终达到的目都是在Android平台上以极度简单的方式加载和展示图片,如果我们 ...
- 图片加载框架Glide的简单使用
图片加载框架Glide的相关使用 一.搭建环境 1.引入依赖,设置网络权限 implementation 'com.github.bumptech.glide:glide:3.7.0' 代码实现 1. ...
- 图片加载框架Picasso - 源码分析
简书:图片加载框架Picasso - 源码分析 前一篇文章讲了Picasso的详细用法,Picasso 是一个强大的图片加载缓存框架,一个非常优秀的开源库,学习一个优秀的开源库,,我们不仅仅是学习它的 ...
- Android Glide图片加载框架(四)回调与监听
文章目录 Android Glide图片加载框架系列文章 Android Glide图片加载框架(一)基本用法 Android Glide图片加载框架(二)源码解析之with() Android Gl ...
最新文章
- windows7下GithubDesktop和极域学生客户端冲突导致无法正常打开解决方案
- Linux grep不包含某些字符串的命令
- 看漫画学python 豆瓣_看漫画就能学好python?
- ICCV 2019 | Lifelong GAN:基于持续学习的条件图像生成模型
- Linux环境下配置Tomat
- HDU4311(排序+二分)
- oracle帮助系统,开启Oracle的帮助系统
- date js 半年_JavaScript Date对象
- 【7】jQuery学习——入门jQuery选择器之过滤选择器-可见性过滤选择器
- 百万数据下几种SQL性能测试
- 快速排序中Partition算法总是从右边开始查找的原因
- 【Matlab学习笔记】【函数学习】size参数
- 面向对象16:代码块、final关键字
- 如何提高平面设计的思维转向
- 医学英文文献怎么找?
- UVA 213 - Message Decoding 简单题 lambda表达式 23333333
- Golang入门之——文件锁操作flock
- 家谱管理系统php,家谱管理系统(含源代码).docx
- vite 设置启动Network 为本地ip
- house of apple2(改进)