您现在的位置: 微信小程序 > 微信小程序开发 > 教程 >

小程序之图片瀑布流(最全实现方式,额外加送懒加载)

来源:微信小程序 编辑:Yiyongtong.com 发布时间:2018-05-16 15:23热度:

来来来,看啊看,外面的世界多好看,

效果图展示的是瀑布流布局 && 懒加载的效果

数据

图片数据来源张鑫旭的网络日志

先说下我们的图片链接格式

.jpg 这样的格式,我们需要改变name的值就行了,当 name 值小于10的时候,格式是 00x ,如 002 、 003 ,大于10的时候就是 023 这种。

定义

瀑布流布局是一种比较流行的页面布局方式, 最早采用此布局的网站是Pinterest, 图片宽度是固定的,高度自动,产生一种参差不齐的美感。

原理

原理很简单,主要分为以下几步

1、定义高度数组和列数

2、遍历元素,个数小于列数的直接 push 到数组中

3、大于列数的,获取高度数组中最小的值,定义元素的top和left值

4、 重要一点 更新高度数组,将最小高度加上当前元素的高度

知道原理了,代码应该怎么写呢?这里用web端来示例,大概如下


let heightArr = []
let col = 2
let allBox = document.querySelectorAll('.box') // 获取所有盒子

for(let i in allBox){
	
	let boxWidth = allBox[0].offsetWidth  // 获取盒子宽度 都一样直接取第一个
  let boxHeight = allBox[i].offsetHeight
	if(i < col){	
		heightArr.push(boxHeight)  // 把第一行高度都添加进去
	} else {  // 进行布局操作
		
		let minHeight = Mac.min.apply(null, heightArr)  // 获取最小高度
		let minIndex = getIndex(heightArr, minHeight)  // 获取最小高度的下标 要不就是0 要不就是1
		allBox[i].style.position = 'absolute'
		allBox[i].style.top = minHeight + 'px'
		allBox[i].style.width = minIndex * boxWidth + 'px'
		
		heightArr[minIndex] += boxHeight // 更新最新高度 
	}
}

// 获取下标
getIndex(arr, val){
	for(i in arr){
		if(arr[i] == val) {
			return i
		}
	}
}

上面就是实现瀑布流的主要逻辑,这里大概写了下,接下来我们看看小程序怎么实现。

实现

在web页面里面我们可以直接获取、操作DOM,实现起来很方便,何况还有很多的jquery插件可以使用。我们知道小程序里面是没有DOM的,那应该怎么实现呢?我们把思路转换下就行了。

这里我们用三种方式来实现瀑布流布局。

CSS

使用css3来实现是最简单的,我们先捡简单的来说,

使用 column-count 属性设置列数

使用 wx-if 进行判断将图片渲染到左侧还是右侧

wxml


<view class='container'>
    <image src='{{item.url}}' wx:if="{{index % 2 != 0 }}" wx:for="{{list}}" mode='widthFix' wx:key="{{index}}"></image>
     <image src='{{item.url}}' wx:if="{{index % 2 == 0 }}" wx:for="{{list}}" mode='widthFix' wx:key="{{index}}"></image> 
</view>

wxss


.container{
  column-count: 2;  /*设置列数*/ 
  column-gap:2rpx;
  padding-left: 8rpx;
}
image{
  width: 182px;
  box-shadow: 2px 2px 4px rgba(0,0,0,.4);
}

js获取下数据即可,这里就不赘述了。

节点信息

小程序可以通过WXML节点信息API来获取元素的信息,接下来我们来撸码。

wxml


<view class="container">
	  <view wx:for="{{group}}" style='position:{{item.position}}; top: {{item.top}}; left:{{item.left}}; width:{{width}}rpx;' class='box box-{{index}}' wx:key="{{index}}">
 			<image  src='http://cued.xunlei.com/demos/publ/img/P_{{item.name}}.jpg' style=' height:{{height[index]}}px' bindload='load' data-index='{{index}}' class='image'></image>
	  </view>
</view>

wxss


.container{
  position: relative;
  display: flow-root;
}
.box{
  float: left;
  display: flex;
  margin-left:5rpx;
  box-shadow: 2rpx 2rpx 5rpx rgba(0,0,0,.3);
  border: 1rpx solid #ccc;
  box-sizing: border-box;
  padding: 10px;
}
.box:nth-child(2){
  margin-left: 12rpx;
}
image{
  width: 100%;
}

js

.jpg , 只需要更改name就行了

首先处理我们的数据


// 创建长度为30的数组
const mockData = () => {
  return Array.from(Array(30).keys()).map(item => {
    if (item < 10) {
      return '00' + item
    } else {
      return '0' + item
    }
  })

}
// 扩展成我们需要的数据
const createGroup = () => {
  let group = []
  let list = mockData()
  list.forEach(item => {
    group.push({ name: item, position: 'static', top: '', left: '' })
  })
  return group
}

然后进行瀑布流布局,主要代码如下


load(e){  // 监听图片加载完 获取图片的高度
    this.setData({
      height: [...this.data.height, e.detail.height]
    })
    this.showImg()  // 调用渲染函数
},

showImg(){
    let height = this.data.height
    if (height.lenth != this.data.group .legth){  // 保证所有图片加载完
      return
    }
    setTimeout(()=>{ // 异步执行
      wx.createSelectorQuery().selectAll('.box').boundingClientRect((ret) => {
        let cols = 2
        var group = this.data.group
        var heightArr = [];
        for (var i = 0; i < ret.length; i++) {
          var boxHeight = height[i]
          if (i < cols) {
            heightArr.push(boxHeight + 25)
          } else {
            var minBoxHeight = Math.min.apply(null, heightArr);
            var minBoxIndex = getMinBoxIndex(minBoxHeight, heightArr);
            group[i].position = 'absolute'
            group[i].top = `${minBoxHeight}px`
            group[i].left = minBoxIndex * this.data.width / 2 + 'px'
            group[i].left = minBoxIndex == 0 ?  minBoxIndex * this.data.width / 2 + 'px' : minBoxIndex * this.data.width / 2 + 5 + 'px'
            heightArr[minBoxIndex] += (boxHeight + 25)
          }
        }

        this.setData({
          group
        })
        wx.hideLoading()

      }).exec()
    }, 200)
    
  }

可以看到实现的逻辑和上面的大概类似,只不过这里我们修改的是数据,毕竟小程序是数据驱动的嘛。

这里主要我们监听 image 组件的 bindload 事件来获取每张图片的高度,获取了高度才能进行布局,大部分的时间也都用来加载图片了,能不能优化呢?当然可以了,我们使用node把数据包装下。

后端处理数据

上面我们说到在小程序内部获取图片的高度是个费力不讨好的事,我们使用node来获取图片高度,然后包装下再给小程序使用。

  • 使用request进行请求
  • 使用image-size获取图片的高度
  • 最后将获取后将数据写入文件,启动一个服务提供接口

这里主要说下碰到的