電商小程序中,用到瀑布流的地方很是多,每次都寫一個瀑布流,重複一次邏輯,做爲程序員,確定是很是不肯意的。
瀑布流的形式都是大同小異,不一樣的是瀑布流中每一個模塊的內容,隨業務而變化。
因此,咱們把瀑布流框架抽象成組件,瀑布流的內容由業務肯定。這樣便可實現組件化和自定義的最大平衡,微信小程序組件源碼。
首先,咱們來看一下瀑布流組件在實際項目中的實際效果。javascript
瀑布流組件實際效果以下圖所示,左側爲用戶交互效果,右側爲圖片懶加載實際效果。
html
瀑布流,又稱瀑布流式佈局。是比較流行的一種網站頁面佈局,waterfall-item
寬度固定,高度不定,視覺表現爲良莠不齊的多欄佈局,隨着頁面滾動條向下滾動,這種佈局還會不斷加載數據塊並附加至當前尾部。以下圖所示:
java
該瀑布流組件實現瞭如下幾個功能:node
swiper
和swiper-item
組件用法waterfall
和waterfall-item
實現原理第一步:在 waterfall-layout 目錄下建立 waterfall
和 waterfall-item
組件,目錄結構以下:git
. ├── query-node.js ├── waterfall-item.js ├── waterfall-item.json ├── waterfall-item.wxml ├── waterfall-item.wxss ├── waterfall.js ├── waterfall.json ├── waterfall.wxml └── waterfall.wxss
第二步:分別在waterfall.js
和 waterfall-item.js
的relations
選項中指定組件父、子級關係:程序員
// waterfall.js Component({ // ... other code relations: { './waterfall-item': { type: 'child', }, // ... other code } })
// waterfall-item.js Component({ // ... other code relations: { '././waterfall': { type: 'parent', }, // ... other code } })
指定彼此的父、子組件的關係後,便可經過 this.getRelationNodes
原生 API,就能訪問彼此實例對象及其屬性和方法。 github
第三步:實現waterfall.wxml
和 waterfall-item.wxml
代碼: waterfall.wxml
代碼實現很是簡單,只有5行代碼:web
<view class="waterfall custom-class"> <view class="waterfall-inner"> <slot ></slot> </view> </view>
一樣,waterfall-item.wxml
代碼實現也很是簡單,只有5行代碼:json
<view class="waterfall-item custom-class" style="{{position}}:0;top:{{(top >= 0 ? top + 'px' : 0 + 'rpx')}};" > <slot ></slot> </view>
不知道slot
用法的童鞋,請參考微信小程序自定義組件模板和樣式文檔。小程序
其實,不論是微信小程序、web、仍是原生APP,瀑布流的實現原理都是同樣的。均可以絕對定位和位置計算來實現。
瀑布流的大致過程以下圖所示:
第一步:數據經過this.setData
從邏輯層傳輸到視圖層,進行第一渲染,因爲每一個waterfall-item
的top:0;
和 position:left;
,因此都重疊了在一塊兒。
第二步:經過節點查詢API獲取每一個waterfall-item
元素信息,而且計算出正確的top
和position
值。
第三步:setData
每一個waterfall-item
的top
和position
,實現重排。
具體邏輯實現以下:
首先,咱們來實現一個節點查詢API querySelector
,以後會用到:
// query-node.js /** * 獲取當前頁面中,選擇器爲 selector 的第一個node節點 * @param {String} selector 符合微信小程序規範的選擇器 * @param {Object} context 調用環境,普通頁面中爲wx,自定義組件中爲this;默認值爲wx. * @return {Array} 返回一個數組,第一個元素爲 node 節點 */ export const querySelector = function (selector, context = wx) { return new Promise((resolve, reject) => { context.createSelectorQuery() .select(selector) .boundingClientRect((res) => { if (res) { resolve(res); } else { reject(`不存在選擇器爲 ${selector} 的節點`); } }) .exec(); }) };
接着,看一下組件waterfall
和waterfall-item
在實際項目中的用法:
<waterfall loading="{{loadMorePending}}" isAllLoaded="{{isAllLoaded}}" > <block wx:for="{{data.sections}}" wx:key="id" wx:for-item="product"> <waterfall-item index="{{index}}" custom-class="flow-item-wrapper" > <view class="product-item"> 業務代碼 </view> </waterfall-item> </block> </waterfall>
當第一個waterfall-item
組件,在視圖層佈局完成後會執行ready
生命週期鉤子。
在 ready
生命週期鉤子中,咱們須要作兩件事:
waterfall
的實例對象,並掛載在waterfall-item
組件的 this
實例對象上。由於以後咱們須要在waterfall-item
組件中修改waterfall
上的數據。waterfall-item
組件的高度,計算waterfall-item
組件的位置信息top
和position
。// waterfall-item.js import { querySelector } from './query-node'; Component({ // ... other code lifetimes: { ready() { const [waterfall] = this.getRelationNodes('./waterfall'); this.parent = waterfall; this.setWaterfallItemPosition(); }, } methods:{ async setWaterfallItemPosition() { querySelector('.waterfall-item', this) .then(async (node) => { const { top, position } = await this.parent.getWaterfallItemPostionInfo(node); this.setData({ top, position }) }) }, } // ... other code })
在setWaterfallItemPosition
方法中,咱們調用了父組件上的方法this.parent.getWaterfallItemPostionInfo
,獲取當前waterfall-item
組件的top
和position
信息。並把已經渲染好的waterfall-item
組件的累計高度緩存在waterfall
的leftHeights
和rightHeights
屬性上,用於計算下一個waterfall-item
組件位置,主要邏輯以下:
// waterfall.js const POSITION_LEFT = 'left'; const POSITION_RIGHT = 'right'; Component({ // ... other code /** * 組件的方法列表 */ methods: { lifetimes: { ready() { this.initParams(); } }, initParams() { this.leftHeights = 0; this.rightHeights = 0; }, /** * 設置 waterfall-item 的高度值 * @param {Object} node waterfall-item 組件位置尺寸數據 */ async getWaterfallItemPostionInfo(node) { let top = 0; let position = POSITION_LEFT; const { height } = node; const { itemGap } = this; if (this.leftHeights <= this.rightHeights) { top = this.leftHeights; if(this.leftHeights === 0) { this.leftHeights += height; } else { top += itemGap; this.leftHeights += (height + itemGap); } } else { position = POSITION_RIGHT; top = this.rightHeights; if(this.rightHeights === 0) { this.rightHeights += height; } else { top += itemGap; this.rightHeights += (height + itemGap); } } return { top, position, } } // ... other code } })
當全部的waterfall-item
重排結束後,瀑布流渲染完成。
微信小程序中,<image>
標籤自己是支持懶加載的,當lazy-load={{true}}
,且在即將進入必定範圍(上下三屏)時纔開始加載。
也就是說,當lazy-load={{true}}
,<image>
標籤初次渲染在視口上下三屏以外時,是不會請求圖片資源的,當<image>
即將進入三屏以內時,纔會加載。
在4.2小節的圖3中,<waterfall-item>
的初始化位置設置成了top:0;
和 position:left;
,因此,都在視口中。若是將top
的值成三屏以外的數值,例如,400vh
或者更大,則<waterfall-item>
重排以後,任然在三屏以外的圖片即會自動懶加載。
<view class="waterfall-item custom-class" style="{{position}}:0;top:{{(top >= 0 ? top + 'px' : itemCount * 100 + 'vh')}};" > <slot ></slot> </view>
Component({ // waterfall-item.js // ... other code lifetimes: { ready() { const { itemCount } = this.data; const [waterfall] = this.getRelationNodes('./waterfall'); waterfall.childCount += 1; this.parent = waterfall; this.setData({ itemCount: itemCount + waterfall.childCount, }) }, }, // ... other code })
由於實現了wx:for
<waterfall-item>
功能,和<swiper-item>
組件同樣,所以翻頁邏輯徹底由用戶本身定製,<waterfall>
和<waterfall-item>
只給你提供翻頁的功能,組件就能夠和瀑布流數據結構徹底解耦。
將列和行中,兩個<waterfall-item>
組件之間的距離定義爲itemGap
,則:
itemGap = waterfall寬度 - (waterfall-item寬度 * 2)
在<waterfall>
的ready
鉤子中,能夠獲取到<waterfall>
組件的寬度;同理,在<waterfall-item>
的ready
鉤子中,能夠獲取到<waterfall-item>
組件的寬度。
在調用getWaterfallItemPostionInfo
以前,獲取到itemGap
的值便可。這樣,在計算<waterfall-item>
的top
值時,除了第一行的<waterfall-item>
的top
值等於0以外,其餘全部<waterfall-item>
的top
值等於:
// this.leftHeights += height + itemGap; // or // this.rightHeights += height + itemGap;
具體代碼實現請查看源碼
經過瀑布流框架抽象,使<waterfall>
和<waterfall-item>
接近原生組件使用體驗,同時使組件與數據徹底解耦。經過巧妙的初始化位置top
設置,使瀑布流具圖片有懶加載的功能。