前面写过vuejs实现的瀑布流布局,《vuejs实现瀑布流布局(一)》和《vuejs实现瀑布流布局(二)》也确实实现了瀑布流布局,但是这个是基于SUI-Mobile实现的无限滚动。
近日稍有空闲,回头重新实现了一下移动端的瀑布流布局,摆脱了移动端UI框架的束缚。
移动端的适配,采用的是adaptive-version2.js,而无限加载采用的是Vue Scroller。
最终实现的效果大致如下:
解决了无限滚动和移动端适配的问题,瀑布流也就只剩下一个难点,怎么样让高度不尽相同的图片能够按左右顺序排列下来,还不至于错乱太多,相差太大。
事实上css也已经为我们提供了解决方案,但是css的方案有一个巨大的缺陷在于:一旦图片发生变动,不论增加还是减少,或者说图片资源本身发生变化,他会将整体所有图片的位置进行重排。就是说只要我们动了任意一张图片,其布局可能会完全不同。
而js的瀑布流,主流方案似乎只有两种(具体更多的方案没有研究),一个利用绝对定位,一个分左右两栏(或者多栏),每栏内部垂直布局,而依据图片高度的不同,将其放入适合的栏目内,实现瀑布流。
我们这里采用的就是左右两栏的布局。
<div class="waterfalls"> <ul> <li class="photo" v-for="item in waterfallsLeft" :key="item.id + item.albumId+Math.random()"> <a href="javascrip:;"> <img :src="item.src" alt=""> </a> <div class="desc-info"> <p>编号:<span>{{item.albumId}}</span></p> </div> <div class="thumbnail"> <div class="thumbnail-desc"> <p>{{item.title}}</p> <span>{{item.thumbnailUrl}}</span> </div> <div class="praise active"> <div> <i>赞</i> <span>{{item.albumId}}</span> </div> </div> </div> </li> </ul> <ul> <li class="photo" v-for="item in waterfallsRight" :key="item.id + item.albumId+Math.random()"> <a href="javascrip:;"> <img :src="item.src" alt=""> </a> <div class="desc-info"> <p>编号:<span>00001</span></p> </div> <div class="thumbnail"> <div class="thumbnail-desc"> <p>双语小学</p> <span>詹天佑</span> </div> <div class="praise active"> <div> <i>赞</i> <span>92</span> </div> </div> </div> </li> </ul> </div>
div.waterfalls作为外层包裹,而里面的两个ul,就是两栏,li就是每一张图片所对应的数据。ul宽度各占50%,然后浮动。剩下的就是怎么样让个张图片自动归位,进入waterfallsLeft和waterfallsRight。
首先请求数据:
getData(done){ let _this = this; axios.get("https://jsonplaceholder.typicode.com/photos", {page: this.page}) .then(res=>res.data) .then(res => { let counts = res.slice((this.page-1) * this.pageCount, this.page * this.pageCount) // console.log(counts.length) counts.forEach(item => { item.src ='http://cued.xunlei.com/demos/publ/img/P_'+ this.randomNum()+ '.jpg' }); this.page++; this.itemsLen = counts.length; this.judgeAllLoaded(counts) self.bottom = self.bottom + 10; if(done) done(); }) }, randomNum(){ // 三位数随机数,162以内 let random = Math.floor(Math.random() * 162) return random = random < 10 ? '00'+random : random < 100 ? '0'+random : ''+random; },
请求的是https://jsonplaceholder.typicode.com/photos,有很多数据可以进行一些模拟请求,但是其返回的图片都是相同大小的,所以为其添加图片资源,item.src ='http://cued.xunlei.com/demos/publ/img/P_'+ this.randomNum()+ '.jpg'。
有了数据,就得按照图片在相同宽度的情况下,高度不同,将其分组分别进入左右两栏this.judgeAllLoaded(counts)。
judgeAllLoaded(items){ // 判断所有图片是否加载完成 let _this = this; items.forEach(item => { let IMG = new Image(); IMG.src = item.src; IMG.width = 100; IMG.onload = function () { _this.flag++; if(_this.leftHeight <= _this.rightHeight){ _this.leftHeight += IMG.height; _this.waterfallsLeft.push(item) }else{ _this.rightHeight += IMG.height; _this.waterfallsRight.push(item) } } }) }
这里关键点在于:IMG.src = item.src; IMG.width = 100; IMG.onload = function;
为IMG对象添加src,并设置相同宽度100,然后等待图片加载完成onload,在图片加载完成之后,判断左右两栏,哪一栏高度较低,该条数据进入哪一栏,这里有一个不大不小的问题,就是由于不确定哪张图片先加载完成,导致即使请求回来的数据相同,其排列的位置也未必相同,但是已经留下了解决flag作为可能的解决方案。就是每加载完成一张图片,flag自增,监听flag的变化,当flag的值与请求回来的数据相等时,再进行数据分配,理论上应该可以解决该问题,留待后面实现。
数据分配完成,其实瀑布流也已经实现了,剩下的就是无限滚动,继续分配请求回来的数据了。
具体代码可以查看GitHub上的GitProjectWaterfalls。