当前位置: 首页 > Web前端 > HTML5

Vue3的图片懒加载自定义说明

时间:2023-04-05 01:17:35 HTML5

以下文章来自老黄前端私房菜。作者黄以煌的出身众所周知。Vue.js的核心思想是数据驱动+组件化。通常,开发一个页面的过程就是我在写一些组件,通过修改数据来驱动组件的重新渲染。在这个过程中,我们不需要手动操作DOM。但是,在某些场景下,我们仍然无法避免对DOM进行操作。由于Vue.js框架接管了创建和更新DOM元素的过程,它可以在DOM元素的生命周期中注入用户代码,因此Vue.js设计并提供了自定义指令,允许用户执行一些底层DOM操作。举一个实际的例子——图片的延迟加载。图片的延迟加载是一种常见的性能优化方法。因为它只加载可见区域的图片,所以可以减少很多不必要的请求,大大提高用户体验。图片懒加载的实现原理也很简单。当图片没有进入可见区域时,我们只需要让img标签的src属性指向一张默认图片即可。进入可见区域后,将其src指向真实图片的地址即可。如果我们要在Vue.js项目中实现图片懒加载,那么使用自定义指令是完美的,那我就带大家用Vue3实现一个图片延迟加载的自定义指令v-lazy。插件为了方便多个项目使用这个指令,我们把它做成插件:constlazyPlugin={install(app,options){app.directive('lazy',{//directiveobject})}}exportdefaultlazyPluginand然后在项目中引用它:import{createApp}from'vue'importAppfrom'./App.vue'importlazyPluginfrom'vue3-lazy'createApp(App).use(lazyPlugin,{//添加一些配置参数})通常一个Vue3插件会暴露install函数,当app实例使用该插件时会执行该函数。在install函数中,通过app.directive注册一个全局指令,以便它们可以在组件中使用。指令的实现接下来我们需要做的是实现指令对象。一个指令定义对象可以提供挂载、更新、卸载等多个钩子函数,我们可以在相应的钩子函数中编写相应的代码来满足需求。在写代码之前,我们不妨想一下实现图片懒加载的几个关键步骤。图片管理管理图片的DOM、真实的src、预加载的url、加载状态、图片的加载情况。可见区域判断判断图片是否进入可见区域。关于图片的管理,我们设计了ImageManager类:constState={loading:0,loaded:1,error:2}exportclassImageManager{constructor(options){this.el=options.elthis.src=options.srcthis.state=State.loadingthis.loading=options.loadingthis.error=options.errorthis.render(this.loading)}render(){this.el.setAttribute('src',src)}load(next){if(this.state>State.loading){return}this.renderSrc(next)}renderSrc(next){loadImage(this.src).then(()=>{this.state=State.loadedthis.render(this.src)next&&next()}).catch((e)=>{this.state=State.errorthis.render(this.error)console.warn(`loadfailedwithsrcimage(${this.src})错误消息是${e.message}`)next&&next()})}}exportdefaultfunctionloadImage(src){returnnewPromise((resolve,reject)=>{constimage=newImage()image.onload=function(){resolve()dispose()}image.onerror=function(e){reject(e)dispose()}image.src=srcfunctiondispose(){image.onload=image.onerror=null}})}首先对于一张图片来说,它有三种状态,正在加载,加载完成和加载失败ImageManager在实例化的时候,除了初始化一些数据,也会在对应img标签的src中执行图片加载,相当于默认加载的图片。当执行ImageManager对象的load方法时,会判断图片的状态。如果它还在加载,它的真实src将被加载。这里使用loadImage图片预加载技术来请求src图片,然后在src成功后替换img标签,修改状态,从而完成图片真实地址的加载。有了图片管理器,接下来我们需要实现可见区域的判断和多个图片管理器的管理,设计Lazy类:constDEFAULT_URL='data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAAIBRAA7'exportdefaultclassLazy{构造函数(选项){this.managerQueue=[]this.initIntersectionObserver()this.loading=options.loading||DEFAULT_URLthis.error=options.error||DEFAULT_URL}add(el,binding){constsrc=binding.valueconstmanager=newImageManager({el,src,loading:this.loading,error:this.error})this.managerQueue.push(manager)this.observer。observe(el)}initIntersectionObserver(){这个。observer=newIntersectionObserver((entries)=>{entries.forEach((entry)=>{if(entry.isIntersecting){constmanager=this.managerQueue.find((manager)=>{returnmanager.el===entry.target})if(manager){if(manager.state===State.loaded){this.removeManager(manager)返回}manager.load()}}})},{rootMargin:'0px',threshold:0})}removeManager(manager){constindex=this.managerQueue.indexOf(manager)if(index>-1){this.managerQueue.splice(index,1)}if(this.observer){this.observer.unobserve(manager.el)}}}constlazyPlugin={install(app,options){constlazy=newLazy(options)app.directive('lazy',{mounted:lazy.add.bind(lazy)})}}这样,每当图像元素绑定到v-lazy命令,执行mounted钩子函数时,就会调用Lazy对象的add方法执行时,第一个参数el对应的是图片对应的DOM元素对象,第二个参数binding是指令对象绑定的值,例如:其中item.pic对应指令绑定值,所以可以通过binding.value获取图片的真实地址。有了图片的DOM元素对象和真实的图片地址后,就可以根据它们创建一个图片管理器对象,并添加到managerQueue中。在可视区域中观察图像DOM元素。对于图片进入可见区域的判断,主要使用IntersectionObserverAPI,其对应的回调函数的参数入口为一个IntersectionObserverEntry对象数组。当被观察元素的可见比例超过指定阈值时,会执行回调函数遍历entry,获取每一个entry,然后判断entry.isIntersecting是否为true,如果为true,则表示对应的DOM元素条目对象已进入查看区域。然后根据DOM元素的比较,从managerQueue中找到对应的manager,判断其对应图片的加载状态。如果图片处于loading状态,此时执行manager.load函数,完成真实图片的加载;如果处于loaded状态,则直接从managerQueue中移除其对应的manager,停止观察图片DOM元素。目前我们已经实现了图片元素挂载到页面后延迟加载的一系列流程。但是,当元素从页面卸载时,它还需要执行一些清理操作:exportdefaultclassLazy{remove(el){constmanager=this.managerQueue.find((manager)=>{==el})if(manager){this.removeManager(manager)}}}constlazyPlugin={install(app,options){constlazy=newLazy(options)app.directive('lazy',{mounted:lazy.add.bind(lazy),remove:lazy.remove.bind(lazy)})}}当元素被卸载时,其对应的图片管理器也会从managerQueue中移除,图片DOM元素的观察也会停止。另外,如果动态修改v-lazy指令绑定的值,即真实图片的请求地址,则需要在指令内部进行相应的修改:exportdefaultclassImageManager{update(src){constcurrentSrc=this.srcif(src!==currentSrc){this.src=srcthis.state=State.loading}}}exportdefaultclassLazy{update(el,binding){constsrc=binding.valueconstmanager=this.managerQueue.find((manager)=>{returnmanager.el===el})if(manager){manager.update(src)}}}constlazyPlugin={install(app,options){constlazy=newLazy(options)app.directive('lazy',{mounted:lazy.add.bind(lazy),remove:lazy.remove.bind(lazy),update:lazy.update.bind(lazy)})}}到目前为止,我们已经实现了一个简单的图片懒加载指令,在此基础上,我们是否还能做一些优化呢?指令优化在加载图片真实url的过程中,我们使用了loadImage来预加载图片。显然,对于具有相同url的多张图片,预加载只需要进行一次。为了实现上述需求,我们可以在Lazy模块内部创建一个cache缓存:exportdefaultclassLazy{constructor(options){//...this.cache=newSet()}}并且在创建ImageManager实例时,把缓存传入:constmanager=newImageManager({el,src,loading:this.loading,error:this.error,cache:this.cache})然后修改ImageManager如下:exportdefaultclassImageManager{load(next){if(this.state>State.loading){return}if(this.cache.has(this.src)){this.state=State.loadedthis.render(this.src)return}this.renderSrc(next)}renderSrc(next){loadImage(this.src).then(()=>{this.state=State.loadedthis.render(this.src)next&&next()}).catch((e)=>{this.state=State.errorthis.cache.add(this.src)this.render(this.error)console.warn(`loadfailedwithsrcimage(${this.src})并且报错msg为${e.message}`)next&&next()})}}每次执行load前先从缓存中判断是否存在,执行loadImage后更新缓存预加载图像成功。通过这种以空间换时间的方式,避免了一些重复的url请求,达到了优化性能的目的。总结懒加载图片指令的完整指令实现,可以在vue3-lazy中查看。懒加载图片命令的核心是通过IntersectionObserverAPI判断图片是否进入可见区域。现代浏览器支持该功能,IE浏览器不支持。这时候可以监听图片的可滚动父元素的一些事件。比如滚动,调整大小等,然后通过一些DOM计算来判断图片元素是否进入可见区域。但是Vue3显然已经不支持IE了,所以只用IntersectionObserverAPI就可以了。除了懒加载图片的自定义指令中使用的钩子函数,Vue3的自定义指令还提供了一些其他的钩子函数。以后开发自定义指令时,可以参考它的文档,找到合适的钩子函数。编写相应的代码逻辑。相关链接[1]IntersectionObserver:https://developer.mozilla.org...[2]vue3-lazy:https://github.com/ustbhuangy...[3]《Vue3 开发高质量音乐 Web app》:https://coding.imooc.com/clas...[4]Vue3自定义指令文档:https://v3.cn.vuejs.org/guide...