瀑布流是一种很常见的网页布局,视觉表现为良莠不齐的多栏布局,是一种时下很流行的布局形式,最近在写小程序刚好也碰到了,想了几种不一样的实现方法,接下来就来一块儿看看具体的实现方法(所用的方法中用的例子都是两栏的布局)。css
等高的瀑布流顾名思义就是瀑布流里的单个盒子的高度都是同样的,这种形式的瀑布流实现起来也比较简单,由于不涉及到盒子高度的计算,举个例子:html
<view class="fall"> <view wx:for="{{list}}" class="fall-item"></view> </view>
Page({ data: { list: [] }, onLoad () { let images = [] for (let i = 0; i < 10; i++) { images.push({ url: 'test' }) } this.setData({ list: images }) } })
.fall { display: flex; flex-wrap: wrap; background-color: #f7f7f7; } .fall-item { width: 330rpx; height: 330rpx; margin-top: 30rpx; margin-left: 30rpx; background-color: aquamarine; }
为了方便,例子中的盒子内容并无使用图片,而是使用了色块代替,等高瀑布流的实现能够直接经过flex布局实现,如例子所示,直接用flex布局,容许换行,设置好瀑布流里中每个盒子的宽高,就能实现简单的实现两栏瀑布流布局前端
不等高瀑布流是更为常见的形式,不等高瀑布流涉及到列高的计算,因为每一个盒子的高度不同,所以须要每一列的列高都要记录、比较,将下一个盒子插入高度矮的一列,接下来就来看看不等高瀑布流的实现方式小程序
通常瀑布流里展现的都是图片,这种状况指的是服务端会返给前端要展现的图片的宽高,这种状况下相对也比较简单,由于服务端会返回图片的宽高,前端只须要计算一下列高,将下一张图片插入矮的那里一列就能够,举个例子:api
<view class="fall"> <view wx:for="{{list}}" wx:for-index="idx" wx:for-item="column" class="fall-column"> <view class="fall-column-item" wx:for="{{column}}" wx:for-index="i" wx:for-item="item" style="height: {{item.showHeight}}rpx"></view> </view> </view>
.fall { display: flex; background-color: #f7f7f7; } .fall-column { display: flex; flex-direction: column; margin-left: 30rpx; } .fall-column-item { width: 330rpx; margin-top: 30rpx; background-color: aquamarine; }
Page({ data: { images: [{ width: 360, height: 540 }, { width: 480, height: 540 }, { width: 540, height: 720 }, { width: 720, height: 960 }, { width: 540, height: 960 }, { width: 360, height: 720 }, { width: 360, height: 960 }, { width: 540, height: 540 }, { width: 540, height: 1440 }, { width: 960, height: 1440 }], heightArr: [], list: [], col: 2 }, onLoad () { this.initData(2) }, initData (col) { let images = [] let scale = 2 // 模拟图片宽高 for (let i = 0; i < 10; i++) { let image = this.data.images[Math.floor(Math.random() * 10)] images.push(image) } for (let i in images) { let height = 165 / images[i].width * images[i].height * scale images[i].showHeight = height // 第一行的两个盒子 if (i < col) { this.data.list.push([images[i]]) this.data.heightArr.push(height) } else { // 选出高度较矮的一列的索引 let minHeight = Math.min.apply(null, this.data.heightArr) let minHeightIndex = this.data.heightArr.indexOf(minHeight) this.data.list[minHeightIndex].push(images[i]) this.data.heightArr[minHeightIndex] += height } } this.setData({ list: this.data.list }) }, onReachBottom () { this.initData(0) } })
上例中为了方便也是用色块模拟了图片,在js中模拟了10张图片的宽高,每次从中随机取10张图片,定义了两列,每次计算一下每列的高度,将图片插入矮的那一列,而后将记录用高度数组,将图片的高度累加,实现起来也很简单数组
未知盒子高度的状况下,咱们要怎么作呢?服务器
第一种办法就是经过wx.getImageInfo能够获取到图片宽高信息,举个例子:app
<view class="fall"> <view class="fall-column" wx:for="{{list}}" wx:for-index="idx" wx:for-item="column" wx:key="{{idx}}"> <view class="fall-column-item" wx:for="{{column}}" wx:for-index="i" wx:key="{{i}}" wx:for-item="item"> <image class="fall-column-item-img" src="{{item.cover}}" mode="widthFix"/> </view> </view> </view>
.fall { display: flex; background-color: #f7f7f7; } .fall-column { display: flex; flex-direction: column; margin-left: 30rpx; } .fall-column-item { margin-top: 30rpx; line-height: 0; } .fall-column-item-img { width: 330rpx; }
import api from '../../api/index' Page({ data: { list: [], heightArr: [] }, async onLoad () { let {results} = await api.fetchImages() let col = 2 for (let i in results) { results[i].cover = results[i].imageUrl // 获取图片信息 let info = await this.loadImage(results[i].cover) results[i].height = 165 / info.width * info.height if (i < col) { this.data.list.push([results[i]]) this.data.heightArr.push(results[i].height) } else { let minHeight = Math.min.apply(null, this.data.heightArr) let minHeightIndex = this.data.heightArr.indexOf(minHeight) this.data.list[minHeightIndex].push(results[i]) this.data.heightArr[minHeightIndex] += results[i].height } } this.setData({ list: this.data.list }) }, loadImage (cover) { return new Promise(resolve => { wx.getImageInfo({ src: cover, success: (res) => { resolve(res) } }) }) } })
当服务端没有返回图片的宽高时,能够直接经过wx.getImageInfo()获取到图片的信息,这里为了避免打乱服务返回时的图片顺序,特地将这个单独用Promise封了一层,就是为了图片加载完一张再获取下一张,可是当图片比较大的时候就会致使加载的时间会很长,会有长时间的白屏:
这是由于wx.getImageInfo()获取图片信息的时候会先将图片下载下来,而后才能获取图片信息,这就致使时间会比较长,可是若是不须要图片加载顺序时能够考虑直接并行加载,不等上一张图片加载完就加载下一张,这样就能更快的展示dom
既然图片加载获取信息时间比较长,那考虑是否能够加上一个默认的图片,这样用户能在第一时间看到有内容展现,图片信息拿到后再将图片显示出来,举个例子:async
<view class="fall"> <view class="fall-column" wx:for="{{list}}" wx:for-index="idx" wx:for-item="column" wx:key="{{idx}}"> <view class="fall-column-item" wx:for="{{column}}" wx:for-index="i" wx:for-item="item" wx:key="{{i}}"> <image class="fall-column-item-img" src="{{item.cover}}" mode="widthFix"/> </view> </view> </view>
.fall { display: flex; background-color: #f7f7f7; } .fall-column { display: flex; flex-direction: column; margin-left: 30rpx; } .fall-column-item { position: relative; margin-top: 30rpx; line-height: 0; background-color: #ccc; } .fall-column-item::after { content: '加载中'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); display: inline-block; color: #666; } .fall-column-item-img { position: relative; width: 330rpx; z-index: 1; }
import api from '../../api/index' Page({ data: { list: [], heightArr: [] }, async onLoad () { let {results} = await api.fetchImages() let col = 2 for (let i = 0; i < col; i++) { this.data.list[i] = new Array(results.length / 2) } this.setData({ list: this.data.list }) for (let i in results) { results[i].cover = results[i].imageUrl let info = await this.loadImage(results[i].cover) results[i].height = 165 / info.width * info.height if (i < col) { this.data.list[i][0] = results[i] this.data.heightArr.push(results[i].height) } else { let minHeight = Math.min.apply(null, this.data.heightArr) let minHeightIndex = this.data.heightArr.indexOf(minHeight) let index = this.data.list[minHeightIndex].filter(Boolean).length this.data.list[minHeightIndex][index] = results[i] this.data.heightArr[minHeightIndex] += results[i].height } } for (let i = 0; i < col; i++) { this.data.list[i] = this.data.list[i].filter(Boolean) } this.setData({ list: this.data.list }) }, loadImage (cover) { return new Promise(resolve => { wx.getImageInfo({ src: cover, success: (res) => { resolve(res) } }) }) } })
这个例子中就在图片没有加载完以前给了一个默认的加载中的显示,固然这只是一个简单的例子,只能提供简单的优化思路,实际中的加载过渡动画必定会设计得更细腻
通常小程序中用到的图片都是存储在云服务器上的,且云服务器通常都会提供在图片请求地址上带参数获取图片信息,以阿里云为例,能够在图片连接上拼接?x-oss-process=image/info,就能获取到图片信息,举个例子:
<view class="fall"> <view class="fall-column" wx:for="{{list}}" wx:for-index="idx" wx:for-item="column" wx:key="{{idx}}"> <view class="fall-column-item" wx:for="{{column}}" wx:for-index="i" wx:for-item="item" wx:key="{{i}}"> <image class="fall-column-item-img" src="{{item.cover}}" mode="widthFix"/> </view> </view> </view>
.fall { display: flex; background-color: #f7f7f7; } .fall-column { display: flex; flex-direction: column; margin-left: 30rpx; } .fall-column-item { position: relative; margin-top: 30rpx; line-height: 0; background-color: #ccc; } .fall-column-item::after { content: '加载中'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); display: inline-block; color: #666; } .fall-column-item-img { position: relative; width: 330rpx; z-index: 1; }
let fetchPicInfo = async (url) => { let [err, result] = await to(testFly.get(`${url}?x-oss-process=image/info`)) if (err) throw err return result.data }
import api from '../../api/index' Page({ data: { list: [], heightArr: [] }, async onLoad () { let {results} = await api.fetchImages() let col = 2 for (let i = 0; i < col; i++) { this.data.list[i] = new Array(results.length / 2) } this.setData({ list: this.data.list }) for (let i in results) { results[i].cover = results[i].imageUrl let info = await api.fetchPicInfo(results[i].cover) results[i].height = 165 / info.ImageWidth.value * info.ImageHeight.value if (i < col) { this.data.list[i][0] = results[i] this.data.heightArr.push(results[i].height) } else { let minHeight = Math.min.apply(null, this.data.heightArr) let minHeightIndex = this.data.heightArr.indexOf(minHeight) let index = this.data.list[minHeightIndex].filter(Boolean).length this.data.list[minHeightIndex][index] = results[i] this.data.heightArr[minHeightIndex] += results[i].height } } for (let i = 0; i < col; i++) { this.data.list[i] = this.data.list[i].filter(Boolean) } this.setData({ list: this.data.list }) } })
经过这个方法能够大大减小图片加载的时间,不须要将图片下载到本地在获取图片信息,而是直接向服务器请求图片信息,再加上每次请求只会返回图片基本信息就几个字段,所以请求时间也很是短,如图:
这样用户能更快看到图片显示,同时也加上了图片加载时的过渡效果,这样体验效果会更好
这篇文章把最近在写小程序时遇到的瀑布流作了一个比较详细的总结,不一样的状况下选择不一样的加载方案,体验效果最好的固然仍是服务端直接返回图片信息,这样能节省不少获取图片信息要花的时间,这样用户体验更优,但愿能对各位在写小程序瀑布流能有所帮助。若是有错误或不严谨的地方,欢迎批评指正,若是喜欢,欢迎点赞