效果圖
來(lái)來(lái)來(lái),看啊看啄栓,外面的世界多好看娄帖,
效果圖展示的是瀑布流布局 && 懶加載的效果
數(shù)據(jù)
圖片數(shù)據(jù)來(lái)源張?chǎng)涡竦木W(wǎng)絡(luò)日志
先說(shuō)下我們的圖片鏈接格式
所有的鏈接都是http://cued.xunlei.com/demos/publ/img/P_${name}.jpg
這樣的格式,我們需要改變name的值就行了昙楚,當(dāng)name
值小于10的時(shí)候近速,格式是00x
,如002
、003
堪旧,大于10的時(shí)候就是023
這種数焊。
定義
瀑布流布局是一種比較流行的頁(yè)面布局方式, 最早采用此布局的網(wǎng)站是Pinterest, 圖片寬度是固定的,高度自動(dòng)崎场,產(chǎn)生一種參差不齊的美感佩耳。
原理
原理很簡(jiǎn)單,主要分為以下幾步
1谭跨、定義高度數(shù)組和列數(shù)
2干厚、遍歷元素,個(gè)數(shù)小于列數(shù)的直接push
到數(shù)組中
3螃宙、大于列數(shù)的蛮瞄,獲取高度數(shù)組中最小的值,定義元素的top和left值
4谆扎、重要一點(diǎn) 更新高度數(shù)組挂捅,將最小高度加上當(dāng)前元素的高度
知道原理了,代碼應(yīng)該怎么寫(xiě)呢堂湖?這里用web端來(lái)示例闲先,大概如下
let heightArr = []
let col = 2
let allBox = document.querySelectorAll('.box') // 獲取所有盒子
for(let i in allBox){
let boxWidth = allBox[0].offsetWidth // 獲取盒子寬度 都一樣直接取第一個(gè)
let boxHeight = allBox[i].offsetHeight
if(i < col){
heightArr.push(boxHeight) // 把第一行高度都添加進(jìn)去
} else { // 進(jìn)行布局操作
let minHeight = Mac.min.apply(null, heightArr) // 獲取最小高度
let minIndex = getIndex(heightArr, minHeight) // 獲取最小高度的下標(biāo) 要不就是0 要不就是1
allBox[i].style.position = 'absolute'
allBox[i].style.top = minHeight + 'px'
allBox[i].style.width = minIndex * boxWidth + 'px'
heightArr[minIndex] += boxHeight // 更新最新高度
}
}
// 獲取下標(biāo)
getIndex(arr, val){
for(i in arr){
if(arr[i] == val) {
return i
}
}
}
上面就是實(shí)現(xiàn)瀑布流的主要邏輯,這里大概寫(xiě)了下无蜂,接下來(lái)我們看看小程序怎么實(shí)現(xiàn)伺糠。
實(shí)現(xiàn)
在web頁(yè)面里面我們可以直接獲取、操作DOM斥季,實(shí)現(xiàn)起來(lái)很方便训桶,何況還有很多的jquery插件可以使用。我們知道小程序里面是沒(méi)有DOM的酣倾,那應(yīng)該怎么實(shí)現(xiàn)呢舵揭?我們把思路轉(zhuǎn)換下就行了。
這里我們用三種方式來(lái)實(shí)現(xiàn)瀑布流布局躁锡。
CSS
使用css3來(lái)實(shí)現(xiàn)是最簡(jiǎn)單的午绳,我們先撿簡(jiǎn)單的來(lái)說(shuō),
使用column-count
屬性設(shè)置列數(shù)
使用wx-if
進(jìn)行判斷將圖片渲染到左側(cè)還是右側(cè)
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; /*設(shè)置列數(shù)*/
column-gap:2rpx;
padding-left: 8rpx;
}
image{
width: 182px;
box-shadow: 2px 2px 4px rgba(0,0,0,.4);
}
js獲取下數(shù)據(jù)即可稚铣,這里就不贅述了箱叁。
節(jié)點(diǎn)信息
小程序可以通過(guò)WXML節(jié)點(diǎn)信息API來(lái)獲取元素的信息墅垮,接下來(lái)我們來(lái)擼碼。
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就行了
首先處理我們的數(shù)據(jù)
// 創(chuàng)建長(zhǎng)度為30的數(shù)組
const mockData = () => {
return Array.from(Array(30).keys()).map(item => {
if (item < 10) {
return '00' + item
} else {
return '0' + item
}
})
}
// 擴(kuò)展成我們需要的數(shù)據(jù)
const createGroup = () => {
let group = []
let list = mockData()
list.forEach(item => {
group.push({ name: item, position: 'static', top: '', left: '' })
})
return group
}
然后進(jìn)行瀑布流布局耕漱,主要代碼如下
load(e){ // 監(jiān)聽(tīng)圖片加載完 獲取圖片的高度
this.setData({
height: [...this.data.height, e.detail.height]
})
this.showImg() // 調(diào)用渲染函數(shù)
},
showImg(){
let height = this.data.height
if (height.lenth != this.data.group .legth){ // 保證所有圖片加載完
return
}
setTimeout(()=>{ // 異步執(zhí)行
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)
}
可以看到實(shí)現(xiàn)的邏輯和上面的大概類(lèi)似算色,只不過(guò)這里我們修改的是數(shù)據(jù),畢竟小程序是數(shù)據(jù)驅(qū)動(dòng)的嘛螟够。
這里主要我們監(jiān)聽(tīng)image
組件的bindload
事件來(lái)獲取每張圖片的高度灾梦,獲取了高度才能進(jìn)行布局,大部分的時(shí)間也都用來(lái)加載圖片了妓笙,能不能優(yōu)化呢若河?當(dāng)然可以了,我們使用node把數(shù)據(jù)包裝下寞宫。
后端處理數(shù)據(jù)
上面我們說(shuō)到在小程序內(nèi)部獲取圖片的高度是個(gè)費(fèi)力不討好的事萧福,我們使用node來(lái)獲取圖片高度,然后包裝下再給小程序使用辈赋。
- 使用request進(jìn)行請(qǐng)求
- 使用image-size獲取圖片的高度
- 最后將獲取后將數(shù)據(jù)寫(xiě)入文件鲫忍,啟動(dòng)一個(gè)服務(wù)提供接口
這里主要說(shuō)下碰到的問(wèn)題
1、request模塊的請(qǐng)求默認(rèn)返回來(lái)的是個(gè)String類(lèi)型的字符串钥屈,使用image-size模塊傳入的必須是Buffer悟民,怎么破呢?在request請(qǐng)求中設(shè)置encoding
為null
即可
2篷就、我們這里爬取了100張圖片射亏,怎么保證都已經(jīng)爬取完了呢?可以這樣寫(xiě)
Promise.all(List.map(item => getImgData(item))) // getImgData函數(shù)是獲取圖片的函數(shù) 會(huì)返回個(gè)promise
3竭业、如果請(qǐng)求了幾次智润,發(fā)現(xiàn)有的圖片獲取不到了,報(bào)錯(cuò)了永品,怎么回事呢做鹰,人家畢竟做了防爬的,恭喜你中獎(jiǎng)了鼎姐,換個(gè)ip再試吧(可以把代碼放在服務(wù)器上面,或者換個(gè)Wi-Fi)更振,其實(shí)我們只需要爬一次就行炕桨,生成完文件還爬干嘛啊。
完整代碼請(qǐng)戳github
我們回到小程序肯腕,此時(shí)接口返回的數(shù)據(jù)如下
可以看到每個(gè)圖片都有高度了献宫,接下來(lái)我們實(shí)現(xiàn)瀑布流布局,等下实撒,我們搞下瀑布流布局的懶加載姊途,關(guān)于小程序的懶加載涉瘾,猛戳了解更多。
怎么實(shí)現(xiàn)呢捷兰?主要分為兩步
1立叛、將元素瀑布流布局
2、創(chuàng)建IntersectionObserver贡茅,進(jìn)行懶加載
先開(kāi)始我們的布局吧
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
通過(guò)show
這個(gè)字段來(lái)進(jìn)行判斷了圖片是否加載秘蛇,
使用一個(gè)view
組件用來(lái)占位,然后更改show
字段就可以顯示圖片了
js
我們使用兩個(gè)for循環(huán)顶考,先來(lái)進(jìn)行布局
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
})
布局完后赁还,創(chuàng)建IntersectionObserver,動(dòng)態(tài)判斷image節(jié)點(diǎn)的顯示
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
})
})
}
最后
我們使用三種方式完成了小程序的瀑布流布局驹沿,還額外完成了基于瀑布流的懶加載艘策。可以發(fā)現(xiàn)使用css最簡(jiǎn)便渊季,雖然小程序不能操作DOM柬焕,但是我們改完數(shù)據(jù)其實(shí)和改變DOM一樣,將觀念轉(zhuǎn)變過(guò)來(lái)梭域,小程序的開(kāi)發(fā)還是很爽的斑举。
最后的最后,各位病涨,周末快樂(lè)富玷。