效果圖
來來來,看啊看,外面的世界多好看,
效果圖展示的是瀑布流佈局 && 懶載入的效果
資料
圖片資料來源張鑫旭的網路日誌
先說下我們的圖片連結格式
所有的連結都是http://cued.xunlei.com/demos/publ/img/P_${name}.jpg
這樣的格式,我們需要改變name的值就行了,當name
值小於10的時候,格式是00x
,如002
、003
,大於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請求中設定encoding
為null
即可
2、我們這裡爬取了100張圖片,怎麼保證都已經爬取完了呢?可以這樣寫
Promise.all(List.map(item => getImgData(item))) // getImgData函式是獲取圖片的函式 會返回個promise
複製程式碼
3、如果請求了幾次,發現有的圖片獲取不到了,報錯了,怎麼回事呢,人家畢竟做了防爬的,恭喜你中獎了,換個ip再試吧(可以把程式碼放在伺服器上面,或者換個Wi-Fi),其實我們只需要爬一次就行,生成完檔案還爬幹嘛啊。
完整程式碼請戳github
我們回到小程式,此時介面返回的資料如下
可以看到每個圖片都有高度了,接下來我們實現瀑布流佈局,等下,我們搞下瀑布流佈局的懶載入,關於小程式的懶載入,猛戳瞭解更多。
怎麼實現呢?主要分為兩步
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一樣,將觀念轉變過來,小程式的開發還是很爽的。
最後的最後,各位,週末快樂。