來來來,看啊看,外面的世界多好看, 效果圖展示的是瀑布流布局 && 懶加載的效果 數(shù)據(jù)圖片數(shù)據(jù)來源張?chǎng)涡竦木W(wǎng)絡(luò)日志 先說下我們的圖片鏈接格式 .jpg 這樣的格式,我們需要改變name的值就行了,當(dāng) name 值小于10的時(shí)候,格式是 00x ,如 002 、 003 ,大于10的時(shí)候就是 023 這種。 定義瀑布流布局是一種比較流行的頁面布局方式, 最早采用此布局的網(wǎng)站是Pinterest, 圖片寬度是固定的,高度自動(dòng),產(chǎn)生一種參差不齊的美感。 原理原理很簡單,主要分為以下幾步 1、定義高度數(shù)組和列數(shù) 2、遍歷元素,個(gè)數(shù)小于列數(shù)的直接 push 到數(shù)組中 3、大于列數(shù)的,獲取高度數(shù)組中最小的值,定義元素的top和left值 4、 重要一點(diǎn) 更新高度數(shù)組,將最小高度加上當(dāng)前元素的高度 知道原理了,代碼應(yīng)該怎么寫呢?這里用web端來示例,大概如下 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)瀑布流的主要邏輯,這里大概寫了下,接下來我們看看小程序怎么實(shí)現(xiàn)。 實(shí)現(xiàn)在web頁面里面我們可以直接獲取、操作DOM,實(shí)現(xiàn)起來很方便,何況還有很多的jquery插件可以使用。我們知道小程序里面是沒有DOM的,那應(yīng)該怎么實(shí)現(xiàn)呢?我們把思路轉(zhuǎn)換下就行了。 這里我們用三種方式來實(shí)現(xiàn)瀑布流布局。 CSS使用css3來實(shí)現(xiàn)是最簡單的,我們先撿簡單的來說, 使用 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)信息小程序可以通過WXML節(jié)點(diǎn)信息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.jpg , 只需要更改name就行了 首先處理我們的數(shù)據(jù) // 創(chuà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)聽圖片加載完 獲取圖片的高度 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)的邏輯和上面的大概類似,只不過這里我們修改的是數(shù)據(jù),畢竟小程序是數(shù)據(jù)驅(qū)動(dòng)的嘛。 這里主要我們監(jiān)聽 image 組件的 bindload 事件來獲取每張圖片的高度,獲取了高度才能進(jìn)行布局,大部分的時(shí)間也都用來加載圖片了,能不能優(yōu)化呢?當(dāng)然可以了,我們使用node把數(shù)據(jù)包裝下。 后端處理數(shù)據(jù)上面我們說到在小程序內(nèi)部獲取圖片的高度是個(gè)費(fèi)力不討好的事,我們使用node來獲取圖片高度,然后包裝下再給小程序使用。
這里主要說下碰到的問題 1、request模塊的請(qǐng)求默認(rèn)返回來的是個(gè)String類型的字符串,使用image-size模塊傳入的必須是Buffer,怎么破呢?在request請(qǐng)求中設(shè)置 encoding 為 null 即可 2、我們這里爬取了100張圖片,怎么保證都已經(jīng)爬取完了呢?可以這樣寫 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è)圖片都有高度了,接下來我們實(shí)現(xiàn)瀑布流布局,等下,我們搞下瀑布流布局的懶加載,關(guān)于小程序的懶加載,猛戳了解更多。 怎么實(shí)現(xiàn)呢?主要分為兩步 1、將元素瀑布流布局 2、創(chuàng)建IntersectionObserver,進(jìn)行懶加載 先開始我們的布局吧 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 這個(gè)字段來進(jìn)行判斷了圖片是否加載, 使用一個(gè) view 組件用來占位,然后更改 show 字段就可以顯示圖片了 js我們使用兩個(gè)for循環(huán),先來進(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 }) }) } |
工作日 8:30-12:00 14:30-18:00
周六及部分節(jié)假日提供值班服務(wù)