宝哥软件园

小程序的图片瀑布流(最完整的实现方法 额外的懒加载)

编辑:宝哥软件园 来源:互联网 时间:2021-12-10

快点,看,看,外面的世界多美,

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

效果图展示了瀑布流布局的延迟加载效果。

数据

图片数据来源张新旭网志

先说说我们的图片链接格式。jpg格式,我们只需要更改name的值。当name的值小于10时,格式为00x,如002和003,大于10时,格式为023。

定义

瀑布布局是一种流行的页面布局方式。Pinterest是第一个采用这种布局的网站。画面宽度固定,高度自动,产生不均匀的美感。

原理

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

1.定义高度数组和列数

2.遍历元素,并将那些少于列数的元素直接推入数组。

3.如果它大于列数,则获取高度数组中的最小值,并定义元素的顶部和左侧值。

4.更新高度数组并将最小高度添加到当前元素的高度非常重要。

我知道原理。我应该如何写代码?这里,以web端为例,大致如下

heightarr=[]Letcol=2 Letalbox=document . queryselectorall('。box ')//获取(让我在所有框中){Letboxwidth=allbox [0]的所有框。offset with//获取第一个字母boxheight=allbox [I]。offset thigh if(icol){ heightarr。push(box height)//将第一行的所有高度相加} else {//Make layout操作让Minh agree=MAC . min . apply(null,heightArr)//获取最小高度让minidex=getIndex(heightArr,Minh agree)//获取最小高度的下标,该下标为0或1allbox [I]。style . position=' absolute ' all box[I]。style . top=Minh 8g ' px ' all box[I]。style . Width=Minin dex * Width ' px ' height arr[Minin dex]=box height//更新最新高度} }//获取下标Getindex (arr,val){ for(I in arr){ if(arr[I]=val){ return I } }以上是实现瀑布流的主要逻辑,大概写在这里,然后连接。

实现

在网页中,我们可以直接获取和操作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=' width fix ' wx : key=' { { index } } '/image image src=' { { item . URL } } ' wx : if=' { { index % 2==0 } } ' wx : for=' { { list } } ' mode=' width fix ' wx : key=' { { index } } '/image/view 010-110容器{ column-count : 2;/*设置列数*/column-gap :2 rpx;左填充left: 8rpx}图像{ width: 182pxbox-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获取图片的高度
  • 最后将获取后将数据写入文件,启动一个服务提供接口

这里主要说下碰到的问题

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

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

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

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

完整代码请戳github

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

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

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

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

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        }) })}
更多资讯
游戏推荐
更多+