美文网首页微信小程序开发让前端飞Web前端之路
小程序之图片瀑布流(最全实现方式,额外加送懒加载)

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

作者: an祭 | 来源:发表于2018-05-16 16:05 被阅读51次

    效果图

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

    image

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

    数据

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

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

    所有的链接都是http://cued.xunlei.com/demos/publ/img/P_${name}.jpg这样的格式,我们需要改变name的值就行了,当name值小于10的时候,格式是00x,如002003,大于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

    图片链接为http://cued.xunlei.com/demos/publ/img/P_${name}.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获取图片的高度
    • 最后将获取后将数据写入文件,启动一个服务提供接口

    这里主要说下碰到的问题

    1、request模块的请求默认返回来的是个String类型的字符串,使用image-size模块传入的必须是Buffer,怎么破呢?在request请求中设置encodingnull即可

    2、我们这里爬取了100张图片,怎么保证都已经爬取完了呢?可以这样写

    Promise.all(List.map(item => getImgData(item)))  // getImgData函数是获取图片的函数 会返回个promise
    
    

    3、如果请求了几次,发现有的图片获取不到了,报错了,怎么回事呢,人家毕竟做了防爬的,恭喜你中奖了,换个ip再试吧(可以把代码放在服务器上面,或者换个Wi-Fi),其实我们只需要爬一次就行,生成完文件还爬干嘛啊。

    完整代码请戳github

    我们回到小程序,此时接口返回的数据如下

    image

    可以看到每个图片都有高度了,接下来我们实现瀑布流布局,等下,我们搞下瀑布流布局的懒加载,关于小程序的懒加载,猛戳了解更多

    怎么实现呢?主要分为两步

    1、将元素瀑布流布局

    2、创建IntersectionObserver,进行懒加载

    先开始我们的布局吧

    wxml

    <view class='container'>
      <view class='pic pic-{{index}}' wx:for="{{list}}" style="height:{{item.height}}px;left:{{item.left}}; top:{{item.top}}; position:{{item.position}}" wx:key="{{item.index}}">
        <image src='{{item.url}}' wx:if="{{item.show}}"></image>
        <view class='default' wx:if="{{!item.show}}"></view>
      </view>
    </view>
    
    

    上面我们使用wx-if通过show这个字段来进行判断了图片是否加载,

    使用一个view组件用来占位,然后更改show字段就可以显示图片了

    js

    我们使用两个for循环,先来进行布局

    let cols = 2
        let list = this.data.list
        let heightArr = [];
    
    
        for(let i in list){
          var boxHeight = list[i].height
          if (i < cols) {
            heightArr.push(boxHeight + 5)
          } else {
            var minBoxHeight = Math.min.apply(null, heightArr);
            var minBoxIndex = getMinBoxIndex(minBoxHeight, heightArr);
            list[i].position = 'absolute'
            list[i].top = `${minBoxHeight}px`
            list[i].left = minBoxIndex * 182 + 'px'
            list[i].left = minBoxIndex == 0 ? minBoxIndex * 182 + 'px' : minBoxIndex * 182 + 4 + 'px'
            heightArr[minBoxIndex] += (boxHeight + 5)
          }
        }
        this.setData({
          list
        })
    
    

    布局完后,创建IntersectionObserver,动态判断image节点的显示

    for (let i in list) {
          
        wx.createIntersectionObserver().relativeToViewport({ bottom: 20 }).observe('.pic-' + i, (ret) => {
            if (ret.intersectionRatio > 0) {
              list[i].show = true
            }
            this.setData({
              list
            })
     })
    }
    
    

    最后

    我们使用三种方式完成了小程序的瀑布流布局,还额外完成了基于瀑布流的懒加载。可以发现使用css最简便,虽然小程序不能操作DOM,但是我们改完数据其实和改变DOM一样,将观念转变过来,小程序的开发还是很爽的。

    最后的最后,各位,周末快乐。

    github

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/grapdftx.html