点击上方前端Q,关注公众号
回复加群,加入前端Q技术交流群
猫咪瀑布流
如下动态图,一张张不规则的可爱猫咪照片是否勾起了你的少女心呢?
瀑布流又称瀑布流式布局,是比较流行的一种网站页面布局方式。瀑布流实现的方式有很多种,但是原理都是差不多的,本文我们来详细介绍下下面这个猫咪瀑布流是如何实现的。
瀑布流原理
如上图:第1、2、3、4、5张图排在容器内的第一行,即靠近顶部。
我们会发现第6张图并没有排在第1张图的下面,而是排在了第3张图下面。
其实这就是瀑布流的关键之处,那么第6张图片是根据什么排列的呢?
其实他会放在当前排列图片中底部距离顶部最小的图片下面,这样做是为了图片差不会很大,我们可以看到3是高度最小的图片,然后我们就将第6张图放在3图的下面。
那么同理,第7张图就应该在下图所示位置。
那么你知道第8张图应该放在哪里吗,这里我们留个问题让大家思考,文章结尾我们会揭晓答案,你要是迫不及待可以滑到文章结尾看看你猜的对不对。
预加载图片
实现瀑布流的原理我们大概知道,那么具体的技术实现是怎么实现呢?
其实就是根据图片宽高等设置图片的偏移值即top和left值。
意味着我们肯定需要知道图片的宽高比例,因为我们这里的一列的宽度需要保持一致,即可以设置一个固定值。
如果我们等渲染完以后再进行高度的获取,然后再设置top值和left值,就会导致界面的闪动。
所以我们需要再一开始就先预加载图片并获取宽高,但是并不进行渲染等时机成熟,也就是所有图片都加载完成,即所有图片的高度都算出来以后再进行渲染,说起来柑橘很简单,但是具体实现应该怎么操作呢?
1.遍历传进来的img数组
//imgsArr是组件外部传入的一个图片数组里面有一个src表示图片的路径this.imgsArr.forEach((imgItem,imgIndex)=>{//...//...})复制代码
2.loadedCount记录加载数量
//声明loadedCount变量记录加载完毕的数量,为了和imgsArr大小作比较,通知加载完毕(包括无图、加载完毕,加载失败的情况)data(){return{loadedCount:0}}复制代码
3.无图的情况下
//无图时将高度记录为0if(!imgItem[this.srcKey]){this.imgsArr[imgIndex]._height="0";this.loadedCount++;//支持无图模式if(this.loadedCount==this.imgsArr.length){this.$emit("preloaded");}return;}复制代码
4.Image对象
//使用ImageAPI当src属性改变并完成加载时执行letoImg=newImage();oImg.src=imgItem[this.srcKey];oImg.onload=oImg.onerror=(e)=>{//...}复制代码
5.加载完成后,计算实际需要渲染图片的高
//理论上预加载图片的高度/预加载图片的宽度=需要渲染图片的高度/图片宽度this.imgsArr[imgIndex]._height=e.type=="load"?Math.round(this.imgWidth_c*(oImg.height/oImg.width)):this.imgWidth_c;复制代码
6.加载失败后,标识失败标记
if(e.type=="error"){this.imgsArr[imgIndex]._error=true;this.$emit("imgError",this.imgsArr[imgIndex]);}复制代码
7.全部加载完后,进行emit preloaded事件
if(this.loadedCount===this.imgsArr.length){this.$emit("preloaded");}复制代码
计算列数
calcuCols(){//需要计算出渲染多少列数据letwaterfallWidth=this.width?this.width:window.innerWidth;//最少渲染一列letcols=Math.max(parseInt(waterfallWidth/this.colWidth),1);//最大不能超过maxCols列returnthis.isMobile?2:Math.min(cols,this.maxCols;}复制代码
使用on/on/on/emit监听加载完毕
//当加载完以后页面开始进行渲染imgsArr_c为真实渲染数组this.$emit("preloaded");this.$on("preloaded",()=>{this.imgsArr_c=this.imgsArr.concat([]);//预加载完成,这时才开始渲染//...});复制代码
使用$nextTick寻找更新时机
当data中的某个属性改变的时候,这个值并不是立即渲染到页面上,而是先放到watcher队列上(异步),只有当前任务空闲的时候才会去执行watcher队列上的任务。所以导致,改变的数据挂载到dom上会有一定的延迟,这也就导致了,当我们在改变属性值的时候,立即通过dom去拿改变的值时发现拿到的值并不是改变的值,而是之前的值。
上面的data也就是对应了我们的imgsArr_c。
this.$nextTick作用:在下次dom更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获得更新后的dom。
this.$nextTick(()=>{//表示欲加载结束this.isPreloading=false;this.waterfall();});复制代码
使用waterfall方法排列(核心)
waterfall(){//选择所有图片this.imgBoxEls=this.$el.getElementsByClassName("img-box");//如果一个都没有则没有东西可以排列故直接返回if(!this.imgBoxEls)return;//声明top、left、height、colwidth即列的宽度lettop,left,height,colWidth=this.isMobile?this.imgBoxEls[0].offsetWidth:this.colWidth;//开始排列的坐标大小如果是从0开始排列则将colsHeightArr置空,colsHeightArr的作用是用来比较当前排列图片中哪个最小if(this.beginIndex==0)this.colsHeightArr=[];//从0开始排列for(leti=this.beginIndex;i<this.imgsArr.length;i++){if(!this.imgBoxEls[i])return;//获取渲染元素的高度height=this.imgBoxEls[i].offsetHeight;if(i<this.cols){//如果小于列数则将第一排的几个元素全部push进数组里面将top置为0left为列坐标乘以列的宽度this.colsHeightArr.push(height);top=0;left=i*colWidth;}else{//当第一行排列完以后算出当前最小的高度letminHeight=Math.min.apply(null,this.colsHeightArr);//最低高低//当第一行排列完以后算出当前最小的索引letminIndex=this.colsHeightArr.indexOf(minHeight);//最低高度的索//新元素的top值即为数组中最小的值top=minHeight;//左边的值即为最小索引乘以列宽left=minIndex*colWidth;//设置元素定位的位置//更新colsHeightArrthis.colsHeightArr[minIndex]=minHeight+height;}//设置单个元素的left、top值this.imgBoxEls[i].style.left=left+"px";this.imgBoxEls[i].style.top=top+"px";}this.beginIndex=this.imgsArr.length;//排列完之后,新增图片从这个索引开始预加载图片和排列}复制代码
添加响应式
window.addEventListener("resize",this.response);response:function(){letold=this.cols;//重新计算列数this.cols=this.calcuCols();//如果列数不变则不需要重新排列if(old===this.cols)return;//列数不变直接退出this.beginIndex=0;//开始排列的元素索引this.waterfall();}复制代码
添加滚动触底
this.scroll();scroll(){this.$refs.scrollEl.addEventListener("scroll",this.scrollFn);}scrollFn(){letscrollEl=this.$refs.scrollEl;//如果正在预加载if(this.isPreloading)return;letminHeight=Math.min.apply(null,this.colsHeightArr);if(scrollEl.scrollTop+scrollEl.offsetHeight>minHeight-this.reachBottomDistance){this.isPreloading=true;this.$emit("scrollReachBottom");}}复制代码
更多细节
更多细节,源码尽在github[2]上,欢迎大家踊跃star!
发布到npm上供大家使用
npminstall@parrotjs/vue-waterfall-S复制代码
具体可以去我的github README.md进行查看
关于本文
作者:安稳
/post/7026253551361851405
往期推荐
TWeb 腾讯前端技术大会精彩回顾(附PPT)
面试题:说说事件循环机制(满分答案来了)
专心工作只想搞钱的前端女程序员的
最后
欢迎加我微信,拉你进技术群,长期交流学习...
欢迎关注「前端Q」,认真学前端,做个专业的技术人...
点个在看支持我吧