以下文章来自老黄前端私房菜。作者黄以煌的出身众所周知。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是指令对象绑定的值,例如:
