当前位置: 首页 > Web前端 > vue.js

《Vue》一张图片手动懒加载-预加载说明

时间:2023-03-31 23:30:39 vue.js

PrefaceES6注意!!最近在优化个人博客前端的时候,看到了自己图片懒加载/预加载的古代码(通过watch监控实现)。虽然实际效果勉强好,但总觉得不够“Vue”,功能也不足。考虑到现有的vue-lazyload插件使得懒加载势在必行,不知能否自己写一个。搜了一波,发现是可行的,而且还挺简单的。这篇文章给了我很多参考。在此之上,我优化并添加了额外的功能。最终没有超过一百行代码。Oli给了,搭建TA就结束了。Q:为什么不用现成的插件?答:代码还是自己爽。据说vue-lazyload不支持单独指定占位图片,不过我已经实现了图片懒加载。下面就为小白简单介绍一下。熟悉这个概念的可以略过,不然看不到。小白转百度或谷歌图片的懒加载指的是首屏加载后不在视野范围内的图片容器/视野外新添加的图片容器,先给容器一个缩略图或者一个默认的占位符图像(尺寸较小);当容器进入或即将进入视野时,在后台下载原图,准备好后替换原图。效果如下,左右分别是懒加载完成前后的效果,这样用户至少可以在原图到达之前有一个缩略图可以看,一定程度上可以缓解他们的不耐烦,优化用户体验;同时,按需加载的特性可以节省流量。对服务器和用户都是好事,所以延迟加载对于小型水管服务器和面临网络拥塞的时候特别有用。实现的关键是先在数组原型中添加两??个自定义方法,后面会用到。这段代码放在调用指令之前//移除数组的指定元素if(!Array.prototype.remove){Array.prototype.remove=function(item){if(!this.length)return;让index=this.indexOf(item);if(index>-1){this.splice(index,1);归还这个;}}}//推入数组当且仅当数组没有元素(对于字符串)if(!Array.prototype.pushIfNew){Array.prototype.pushIfNew=function(...item){for(让iofitem)if(this.indexOf(i)===-1)this.push(i);returnthis}}判断懒加载位置的关键之一就是判断图片容器是否在视野内。在这里,使用了节点的getBoundingClientRect()方法。返回值是一个DOMRect对象,包括元素块的边框到视野左上角的距离。属性如下如果视图高度为screenHeight,结合上面的属性,我们可以很容易判断元素是否在视图之内lettop=el.getBoundingClientRect().top;letscreenHeight=window.innerHeight||document.documentElement.clientHeight;if(top-50){//不需要严格进入视野,可以适当“扩大”视野,可以判断“comingsoon”的情况,更符合实际需求}后台加载懒加载的第二个关键是后台加载原图实现起来很简单。当分配了img元素的src属性时,加载就会发生。加载成功后,会执行其onload方法。如果失败,将执行onerror方法。利用这个特性,当target进入视野时,可以创建一个临时的img(不需要插入document),定义它加载成功和失败的行为,然后给它的src赋值letimg=newImage();img.onload=()=>{//成功后替换缩略图//...}img.onerror=()=>{//失败后可以显示错误图片//或者什么都不做保持之前的缩略图//...}img.src='originalimgSrc'监控跟踪键三是监控跟踪目标。可以定义两个数组,listenList存放被跟踪的目标,imgCacheList存放加载(缓存)图片的src。当一个img元素新插入到文档中时,依次选择以下三种操作之一。如果原图在imgCacheList中,直接将src赋值给原图。如果img在视野范围内,则会触发后台加载。加载成功后,其src会加入imgCacheList,如果img在视野外,则加入listenList进行监听。对于listenList,我们将绑定全局滚动事件。窗口滚动时,会判断listenList中所有target的位置。如果在视野内,则触发后台加载,加载成功后,将其src添加到imgCacheList中,同时将target从listenList中移除。如果它不在视线范围内,则什么也不做。当然,直接绑定滚动时间会非常频繁地触发该功能。这里可以对功能进行防抖处理。如果监听的img被移除(比如页面跳转),需要在指令注册中移除listenList中对应的监听目标,因为listenList和imgCacheList需要共享管理,所以不能简单的注册全局instructionVue.directive(),但是在另外开辟一个区域来存放这些共享数据,这就需要以插件的形式注册命令。同时该命令有多个钩子函数。考虑到需要在文档中插入img,可以通过getBoundingClientRect()获取位置信息。在这里,插入被选中。inserted钩子函数:绑定元素插入到父节点时调用(只保证父节点存在,不一定插入到文档中)。虽然和需求有偏差,但是这个是最接近需求的钩子函数,也实际使用了。没问题,就选他吧。/*--------lazyload.js--------*/exportdefault(Vue,options)=>{letlistenList=[];让imgCacheList=[];//.....Vue.directive('lazyload',{inserted:(el,binding)=>{},unbind:(el)=>{}}}/*--------main.js-------*/importLazyLoadfrom'./lazyload';Vue.use(LazyLoad)理解了这几个关键点之后,我觉得最后的实现应该有一个大概的思路了,剩下的细节在注释中给出,详见下面完整代码//----lazyload.js----//防抖功能throttle(func,wait){letcontext,args;letprevious=0;returnfunction(){letnow=+newDate();context=this;args=arguments;if(now-previous>wait){func.apply(context,args);previous=now;}}}exportdefault(Vue,options={})=>{//默认设置,可以传入options重写//preloadClass:占用状态的类(原图未加载),可以配合css添加模糊效果//loadErrorClass:图片加载失败后赋值的类//default:默认占位图片透明度//error:加载失败后显示的图片默认情况下错误是透明的,只有在启用错误处理时才会生效letinit={preloadClass:'lazyload-preload',loadErrorClass:'lazyload-status-fail',default:'data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==',error:`data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==`,...options};让listenList=[];让imgCacheList=[];//判断图片是否缓存constisAlreadyLoad=(imgSrc)=>{returnimgCacheList.indexOf(imgSrc)>-1;};//如果在视图中,触发后台加载并返回true,否则返回falseconsttryLoad=(item)=>{let{el,src}=item;让top=el.getBoundingClientRect().top;让screenHeight=window.innerHeight||文档.文档元素。客户身高;if(top-50){让img=newImage();//后台加载后:替换、添加缓存、移除监视器、更新类img.onload=()=>{el.src=src;el.classList.remove(init.preloadClass);imgCacheList.pushIfNew(src);listenList.remove(item);};//如果发生错误:更新类,移除监视器img.onerror=()=>{if(item.errorHandle){el.src=init.error;el.style.objectFit='无';}el.classList.remove(init.preloadClass);el.classList.add(init.loadErrorClass);listenList.remove(项目);};//开始后台加载img.src=src;返回真;}else{返回错误;}};//用于标记监听状态,保证只有addEventListener(第一个插入的图片触发)letlistenStatus=false;constlistenScroll=()=>{if(!listenStatus){window.addEventListener('scroll',throttle(()=>{letlen=listenList.length;for(leti=0;i{letimgSrc,placeholder;//两种传参方式if(typeofvalue==='string'){imgSrc=value;placeholder=init.default;}else{imgSrc=value[0];placeholder=value[1]||init.default;}//如果已经有缓存,直接使用if(isAlreadyLoad(imgSrc)){el.src=imgSrc;returnfalse;}letitem={el:el,src:imgSrc,errorHandle:!!modifiers.rude//是否开启错误处理};//先给占位符图片和占位符类el.src=placeholder;el.classList.add(init.preloadClass);if(tryLoad(item)){return;}//如果在视图之外,加入监听器listenList.pushIfNew(item);//第一张插入的图片负责addEventListener!listenStatus&&listenScroll();},//监控图片移除,解除绑定:(el)=>{for(letitemoflistenList)if(item.el===el){listenList.remove(item);//console.log('remove')}}})}用法同Vue的插件/*-------main.js--------*/importLazyLoadfrom'./lazyload';Vue.use(LazyLoad)/*--------xxx.vue--------*///两种传参方式,指定原图和占位图/只指定原图,占位图默认为//启用错误处理Others目前这条指令只支持img标签的懒加载,不支持background-image等背景图片(因为我的博客用的比较少),但是我觉得实现“区分两者并不难通过修改指令的情况,改变tryLoad函数...”应该可以,而且不支持动态响应参数(不知道对不对),也就是说,如果传入的imgSrc命令改变,绑定元素不会更新,所以currently这个命令只适用于插入一次后不会改变的元素也可以提一下,就像我也想让这个命令更精确