来来来,看啊看,外面的世界多好看,javascript
效果图展现的是瀑布流布局 && 懒加载的效果css
图片数据来源张鑫旭的网络日志html
先说下咱们的图片连接格式java
全部的连接都是http://cued.xunlei.com/demos/publ/img/P_${name}.jpg
这样的格式,咱们须要改变name的值就好了,当name
值小于10的时候,格式是00x
,如002
、003
,大于10的时候就是023
这种。node
瀑布流布局是一种比较流行的页面布局方式, 最先采用此布局的网站是Pinterest, 图片宽度是固定的,高度自动,产生一种良莠不齐的美感。jquery
原理很简单,主要分为如下几步css3
一、定义高度数组和列数git
二、遍历元素,个数小于列数的直接push
到数组中github
三、大于列数的,获取高度数组中最小的值,定义元素的top和left值web
四、重要一点 更新高度数组,将最小高度加上当前元素的高度
知道原理了,代码应该怎么写呢?这里用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的,那应该怎么实现呢?咱们把思路转换下就好了。
这里咱们用三种方式来实现瀑布流布局。
使用css3来实现是最简单的,咱们先捡简单的来讲,
使用column-count
属性设置列数
使用wx-if
进行判断将图片渲染到左侧仍是右侧
<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>
.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来获取元素的信息,接下来咱们来撸码。
<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%; }
图片连接为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模块的请求默认返回来的是个String类型的字符串,使用image-size模块传入的必须是Buffer,怎么破呢?在request请求中设置encoding
为null
便可
二、咱们这里爬取了100张图片,怎么保证都已经爬取完了呢?能够这样写
Promise.all(List.map(item => getImgData(item))) // getImgData函数是获取图片的函数 会返回个promise
三、若是请求了几回,发现有的图片获取不到了,报错了,怎么回事呢,人家毕竟作了防爬的,恭喜你中奖了,换个ip再试吧(能够把代码放在服务器上面,或者换个Wi-Fi),其实咱们只须要爬一次就行,生成完文件还爬干吗啊。
完整代码请戳github
咱们回到小程序,此时接口返回的数据以下
能够看到每一个图片都有高度了,接下来咱们实现瀑布流布局,等下,咱们搞下瀑布流布局的懒加载,关于小程序的懒加载,猛戳了解更多。
怎么实现呢?主要分为两步
一、将元素瀑布流布局
二、建立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 }) }) }
咱们使用三种方式完成了小程序的瀑布流布局,还额外完成了基于瀑布流的懒加载。能够发现使用css最简便,虽然小程序不能操做DOM,可是咱们改完数据其实和改变DOM同样,将观念转变过来,小程序的开发仍是很爽的。
最后的最后,各位,周末快乐。