小程序瀑布流組件:支持翻頁與圖片懶加載

電商小程序中,用到瀑布流的地方很是多,每次都寫一個瀑布流,重複一次邏輯,做爲程序員,確定是很是不肯意的。
瀑布流的形式都是大同小異,不一樣的是瀑布流中每一個模塊的內容,隨業務而變化。
因此,咱們把瀑布流框架抽象成組件,瀑布流的內容由業務肯定。這樣便可實現組件化和自定義的最大平衡,微信小程序組件源碼
首先,咱們來看一下瀑布流組件在實際項目中的實際效果。javascript

1 實際效果

瀑布流組件實際效果以下圖所示,左側爲用戶交互效果,右側爲圖片懶加載實際效果。
瀑布流組件效果圖html

2 什麼是瀑布流?

瀑布流,又稱瀑布流式佈局。是比較流行的一種網站頁面佈局,waterfall-item寬度固定,高度不定,視覺表現爲良莠不齊的多欄佈局,隨着頁面滾動條向下滾動,這種佈局還會不斷加載數據塊並附加至當前尾部。以下圖所示:
瀑布流示意圖java

3 實現功能

該瀑布流組件實現瞭如下幾個功能:node

  • 支持圖片懶加載
  • 支持上拉數據翻頁
  • 支持自定義樣式
  • 支持瀑布流Item間隔底層自動計算
  • 原生組件模式:即類swiperswiper-item 組件用法
  • 組件與數據徹底解耦

4 實現原理

4.1 waterfallwaterfall-item實現原理

第一步:在 waterfall-layout 目錄下建立 waterfallwaterfall-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.jswaterfall-item.jsrelations選項中指定組件父、子級關係:程序員

// 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.wxmlwaterfall-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用法的童鞋,請參考微信小程序自定義組件模板和樣式文檔。小程序

4.2 瀑布流原理

其實,不論是微信小程序、web、仍是原生APP,瀑布流的實現原理都是同樣的。均可以絕對定位位置計算來實現。
瀑布流的大致過程以下圖所示:
第一步:數據經過this.setData從邏輯層傳輸到視圖層,進行第一渲染,因爲每一個waterfall-itemtop:0;position:left;,因此都重疊了在一塊兒。
第二步:經過節點查詢API獲取每一個waterfall-item元素信息,而且計算出正確的topposition值。
第三步setData每一個waterfall-itemtopposition,實現重排。


具體邏輯實現以下:

首先,咱們來實現一個節點查詢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();
  })
};

接着,看一下組件waterfallwaterfall-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組件的位置信息topposition
// 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組件的topposition信息。並把已經渲染好的waterfall-item組件的累計高度緩存在waterfallleftHeightsrightHeights屬性上,用於計算下一個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重排結束後,瀑布流渲染完成。

4.3 圖片懶加載原理

微信小程序中,<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
})

4.4 數據翻頁

由於實現了wx:for <waterfall-item>功能,和<swiper-item>組件同樣,所以翻頁邏輯徹底由用戶本身定製,<waterfall><waterfall-item>只給你提供翻頁的功能,組件就能夠和瀑布流數據結構徹底解耦。

4.5 瀑布流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;

具體代碼實現請查看源碼

5 總結

經過瀑布流框架抽象,使<waterfall><waterfall-item>接近原生組件使用體驗,同時使組件與數據徹底解耦。經過巧妙的初始化位置top設置,使瀑布流具圖片有懶加載的功能。

相關文章
相關標籤/搜索