100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > 使用 Vue 从零开始手写一个猫咪瀑布流组件

使用 Vue 从零开始手写一个猫咪瀑布流组件

时间:2022-07-01 07:55:47

相关推荐

使用 Vue 从零开始手写一个猫咪瀑布流组件

点击上方前端Q,关注公众号

回复加群,加入前端Q技术交流群

猫咪瀑布流

如下动态图,一张张不规则的可爱猫咪照片是否勾起了你的少女心呢?

瀑布流又称瀑布流式布局,是比较流行的一种网站页面布局方式。瀑布流实现的方式有很多种,但是原理都是差不多的,本文我们来详细介绍下下面这个猫咪瀑布流是如何实现的。

瀑布流原理

mao.jpg

如上图:第1、2、3、4、5张图排在容器内的第一行,即靠近顶部。

我们会发现第6张图并没有排在第1张图的下面,而是排在了第3张图下面。

其实这就是瀑布流的关键之处,那么第6张图片是根据什么排列的呢?

其实他会放在当前排列图片中底部距离顶部最小的图片下面,这样做是为了图片差不会很大,我们可以看到3是高度最小的图片,然后我们就将第6张图放在3图的下面。

那么同理,第7张图就应该在下图所示位置。

seven.jpg

那么你知道第8张图应该放在哪里吗,这里我们留个问题让大家思考,文章结尾我们会揭晓答案,你要是迫不及待可以滑到文章结尾看看你猜的对不对。

预加载图片

实现瀑布流的原理我们大概知道,那么具体的技术实现是怎么实现呢?

其实就是根据图片宽高等设置图片的偏移值即topleft值。

意味着我们肯定需要知道图片的宽高比例,因为我们这里的一列的宽度需要保持一致,即可以设置一个固定值。

如果我们等渲染完以后再进行高度的获取,然后再设置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");}复制代码

计算列数

mao4.jpg

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监听加载完毕

mao2.jpg

//当加载完以后页面开始进行渲染imgsArr_c为真实渲染数组this.$emit("preloaded");this.$on("preloaded",()=>{this.imgsArr_c=this.imgsArr.concat([]);//预加载完成,这时才开始渲染//...});复制代码

使用$nextTick寻找更新时机

mao1.jpg

当data中的某个属性改变的时候,这个值并不是立即渲染到页面上,而是先放到watcher队列上(异步),只有当前任务空闲的时候才会去执行watcher队列上的任务。所以导致,改变的数据挂载到dom上会有一定的延迟,这也就导致了,当我们在改变属性值的时候,立即通过dom去拿改变的值时发现拿到的值并不是改变的值,而是之前的值。

上面的data也就是对应了我们的imgsArr_c。

this.$nextTick作用:在下次dom更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获得更新后的dom。

this.$nextTick(()=>{//表示欲加载结束this.isPreloading=false;this.waterfall();});复制代码

使用waterfall方法排列(核心)

mao3.jpg

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;//排列完之后,新增图片从这个索引开始预加载图片和排列}复制代码

添加响应式

mao5.jpg

window.addEventListener("resize",this.response);response:function(){letold=this.cols;//重新计算列数this.cols=this.calcuCols();//如果列数不变则不需要重新排列if(old===this.cols)return;//列数不变直接退出this.beginIndex=0;//开始排列的元素索引this.waterfall();}复制代码

添加滚动触底

mao7.jpg

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进行查看

eight.jpg

关于本文

作者:安稳

/post/7026253551361851405

往期推荐

TWeb 腾讯前端技术大会精彩回顾(附PPT)

面试题:说说事件循环机制(满分答案来了)

专心工作只想搞钱的前端女程序员的

最后

欢迎加我微信,拉你进技术群,长期交流学习...

欢迎关注「前端Q」,认真学前端,做个专业的技术人...

点个在看支持我吧

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。