來來來,看啊看,外面的世界多好看,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同樣,將觀念轉變過來,小程序的開發仍是很爽的。
最後的最後,各位,週末快樂。