快点,看,看,外面的世界多美,
效果图展示了瀑布流布局的延迟加载效果。
图片数据来源张新旭网志
先说说我们的图片链接格式。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,那么应该如何实现呢?我们换个思路吧。
这里我们用三种方式来实现瀑布流布局。
用css3实现最简单。让我们从一个简单的陈述开始。
使用column-count属性设置列数。
使用wx-if确定是向左还是向右渲染图片。
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来获取元素的信息,接下来我们来撸码。
<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>
.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%;}
.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来获取图片高度,然后包装下再给小程序使用。
这里主要说下碰到的问题
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
我们回到小程序,此时接口返回的数据如下
可以看到每个图片都有高度了,接下来我们实现瀑布流布局,等下,我们搞下瀑布流布局的懒加载,关于小程序的懒加载,猛戳了解更多。
怎么实现呢?主要分为两步
1、将元素瀑布流布局
2、创建IntersectionObserver,进行懒加载
先开始我们的布局吧
<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字段就可以显示图片了
我们使用两个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 }) })}