中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

微信小程序中如何實現virtual-list的方法

發布時間:2021-01-19 10:47:21 來源:億速云 閱讀:532 作者:小新 欄目:移動開發

這篇文章將為大家詳細講解有關微信小程序中如何實現virtual-list的方法,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。

背景

小程序在很多場景下面會遇到長列表的交互,當一個頁面渲染過多的wxml節點的時候,會造成小程序頁面的卡頓和白屏。原因主要有以下幾點:

1.列表數據量大,初始化setData和初始化渲染列表wxml耗時都比較長;

2.渲染的wxml節點比較多,每次setData更新視圖都需要創建新的虛擬樹,和舊樹的diff操作耗時比較高;

3.渲染的wxml節點比較多,page能夠容納的wxml是有限的,占用的內存高。

微信小程序本身的scroll-view沒有針對長列表做優化,官方組件recycle-view就是一個類似virtual-list的長列表組件。現在我們要剖析虛擬列表的原理,從零實現一個小程序的virtual-list。

實現原理

首先我們要了解什么是virtual-list,這是一種初始化只加載「可視區域」及其附近dom元素,并且在滾動過程中通過復用dom元素只渲染「可視區域」及其附近dom元素的滾動列表前端優化技術。相比傳統的列表方式可以到達極高的初次渲染性能,并且在滾動過程中只維持超輕量的dom結構。

虛擬列表最重要的幾個概念:

  • 可滾動區域:比如列表容器的高度是600,內部元素的高度之和超過了容器高度,這一塊區域就可以滾動,就是「可滾動區域」;

  • 可視區域:比如列表容器的高度是600,右側有縱向滾動條可以滾動,視覺可見的內部區域就是「可視區域」。

實現虛擬列表的核心就是監聽scroll事件,通過滾動距離offset和滾動的元素的尺寸之和totalSize動態調整「可視區域」數據渲染的頂部距離和前后截取索引值,實現步驟如下:

1.監聽scroll事件的scrollTop/scrollLeft,計算「可視區域」起始項的索引值startIndex和結束項索引值endIndex;

2.通過startIndex和endIndex截取長列表的「可視區域」的數據項,更新到列表中;

3.計算可滾動區域的高度和item的偏移量,并應用在可滾動區域和item上。

微信小程序中如何實現virtual-list的方法

1.列表項的寬/高和滾動偏移量

在虛擬列表中,依賴每一個列表項的寬/高來計算「可滾動區域」,而且可能是需要自定義的,定義itemSizeGetter函數來計算列表項寬/高。

itemSizeGetter(itemSize) {      return (index: number) => {        if (isFunction(itemSize)) {          return itemSize(index);
        }        return isArray(itemSize) ? itemSize[index] : itemSize;
      };
    }復制代碼

滾動過程中,不會計算沒有出現過的列表項的itemSize,這個時候會使用一個預估的列表項estimatedItemSize,目的就是在計算「可滾動區域」高度的時候,沒有測量過的itemSize用estimatedItemSize代替。

getSizeAndPositionOfLastMeasuredItem() {    return this.lastMeasuredIndex >= 0
      ? this.itemSizeAndPositionData[this.lastMeasuredIndex]
      : { offset: 0, size: 0 };
  }

getTotalSize(): number {    const lastMeasuredSizeAndPosition = this.getSizeAndPositionOfLastMeasuredItem();    return (
      lastMeasuredSizeAndPosition.offset +
      lastMeasuredSizeAndPosition.size +
      (this.itemCount - this.lastMeasuredIndex - 1) * this.estimatedItemSize
    );
  }復制代碼

這里看到了是直接通過緩存命中最近一個計算過的列表項的itemSize和offset,這是因為在獲取每一個列表項的兩個參數時候,都對其做了緩存。

 getSizeAndPositionForIndex(index: number) {    if (index > this.lastMeasuredIndex) {      const lastMeasuredSizeAndPosition = this.getSizeAndPositionOfLastMeasuredItem();      let offset =
        lastMeasuredSizeAndPosition.offset + lastMeasuredSizeAndPosition.size;      for (let i = this.lastMeasuredIndex + 1; i <= index; i++) {        const size = this.itemSizeGetter(i);        this.itemSizeAndPositionData[i] = {
          offset,
          size,
        };

        offset += size;
      }      this.lastMeasuredIndex = index;
    }    return this.itemSizeAndPositionData[index];
 }復制代碼

2.根據偏移量搜索索引值

在滾動過程中,需要通過滾動偏移量offset計算出展示在「可視區域」首項數據的索引值,一般情況下可以從0開始計算每一列表項的itemSize,累加到一旦超過offset,就可以得到這個索引值。但是在數據量太大和頻繁觸發的滾動事件中,會有較大的性能損耗。好在列表項的滾動距離是完全升序排列的,所以可以對已經緩存的數據做二分查找,把時間復雜度降低到 O(lgN) 。

js代碼如下:

  findNearestItem(offset: number) {
    offset = Math.max(0, offset);    const lastMeasuredSizeAndPosition = this.getSizeAndPositionOfLastMeasuredItem();    const lastMeasuredIndex = Math.max(0, this.lastMeasuredIndex);    if (lastMeasuredSizeAndPosition.offset >= offset) {      return this.binarySearch({        high: lastMeasuredIndex,        low: 0,
        offset,
      });
    } else {      return this.exponentialSearch({        index: lastMeasuredIndex,
        offset,
      });
    }
  }

 private binarySearch({
    low,
    high,
    offset,
  }: {    low: number;
    high: number;
    offset: number;
  }) {    let middle = 0;    let currentOffset = 0;    while (low <= high) {
      middle = low + Math.floor((high - low) / 2);
      currentOffset = this.getSizeAndPositionForIndex(middle).offset;      if (currentOffset === offset) {        return middle;
      } else if (currentOffset < offset) {
        low = middle + 1;
      } else if (currentOffset > offset) {
        high = middle - 1;
      }
    }    if (low > 0) {      return low - 1;
    }    return 0;
  }復制代碼

對于搜索沒有緩存計算結果的查找,先使用指數查找縮小查找范圍,再使用二分查找。

private exponentialSearch({
    index,
    offset,
  }: {    index: number;
    offset: number;
  }) {    let interval = 1;    while (
      index < this.itemCount &&      this.getSizeAndPositionForIndex(index).offset < offset
    ) {
      index += interval;
      interval *= 2;
    }    return this.binarySearch({      high: Math.min(index, this.itemCount - 1),      low: Math.floor(index / 2),
      offset,
    });
  }
}復制代碼

3.計算startIndex、endIndex

我們知道了「可視區域」尺寸containerSize,滾動偏移量offset,在加上預渲染的條數overscanCount進行調整,就可以計算出「可視區域」起始項的索引值startIndex和結束項索引值endIndex,實現步驟如下:

1.找到距離offset最近的索引值,這個值就是起始項的索引值startIndex;

2.通過startIndex獲取此項的offset和size,再對offset進行調整;

3.offset加上containerSize得到結束項的maxOffset,從startIndex開始累加,直到越過maxOffset,得到結束項索引值endIndex。

js代碼如下:

 getVisibleRange({
    containerSize,
    offset,
    overscanCount,
  }: {    containerSize: number;
    offset: number;
    overscanCount: number;
  }): { start?: number; stop?: number } {    const maxOffset = offset + containerSize;    let start = this.findNearestItem(offset);    const datum = this.getSizeAndPositionForIndex(start);
    offset = datum.offset + datum.size;    let stop = start;    while (offset < maxOffset && stop < this.itemCount - 1) {
      stop++;
      offset += this.getSizeAndPositionForIndex(stop).size;
    }    if (overscanCount) {
      start = Math.max(0, start - overscanCount);
      stop = Math.min(stop + overscanCount, this.itemCount - 1);
    }    return {
      start,
      stop,
    };
}復制代碼

3.監聽scroll事件,實現虛擬列表滾動

現在可以通過監聽scroll事件,動態更新startIndex、endIndex、totalSize、offset,就可以實現虛擬列表滾動。

js代碼如下:

  getItemStyle(index) {      const style = this.styleCache[index];      if (style) {        return style;
      }      const { scrollDirection } = this.data;      const {
        size,
        offset,
      } = this.sizeAndPositionManager.getSizeAndPositionForIndex(index);      const cumputedStyle = styleToCssString({        position: 'absolute',        top: 0,        left: 0,        width: '100%',
        [positionProp[scrollDirection]]: offset,
        [sizeProp[scrollDirection]]: size,
      });      this.styleCache[index] = cumputedStyle;      return cumputedStyle;
  },
  
  observeScroll(offset: number) {      const { scrollDirection, overscanCount, visibleRange } = this.data;      const { start, stop } = this.sizeAndPositionManager.getVisibleRange({        containerSize: this.data[sizeProp[scrollDirection]] || 0,
        offset,
        overscanCount,
      });      const totalSize = this.sizeAndPositionManager.getTotalSize();      if (totalSize !== this.data.totalSize) {        this.setData({ totalSize });
      }      if (visibleRange.start !== start || visibleRange.stop !== stop) {        const styleItems: string[] = [];        if (isNumber(start) && isNumber(stop)) {          let index = start - 1;          while (++index <= stop) {
            styleItems.push(this.getItemStyle(index));
          }
        }        this.triggerEvent('render', {          startIndex: start,          stopIndex: stop,
          styleItems,
        });
      }      this.data.offset = offset;      this.data.visibleRange.start = start;      this.data.visibleRange.stop = stop;
  },復制代碼

在調用的時候,通過render事件回調出來的startIndex, stopIndex,styleItems,截取長列表「可視區域」的數據,在把列表項目的itemSize和offset通過絕對定位的方式應用在列表上

代碼如下:

let list = Array.from({ length: 10000 }).map((_, index) => index);

Page({  data: {    itemSize: index => 50 * ((index % 3) + 1),    styleItems: null,    itemCount: list.length,    list: [],
  },
  onReady() {    this.virtualListRef =      this.virtualListRef || this.selectComponent('#virtual-list');
  },

  slice(e) {    const { startIndex, stopIndex, styleItems } = e.detail;    this.setData({      list: list.slice(startIndex, stopIndex + 1),
      styleItems,
    });
  },

  loadMore() {
    setTimeout(() => {      const appendList = Array.from({ length: 10 }).map(        (_, index) => list.length + index,
      );
      list = list.concat(appendList);      this.setData({        itemCount: list.length,        list: this.data.list.concat(appendList),
      });
    }, 500);
  },
});復制代碼
<view class="container">
  <virtual-list scrollToIndex="{{ 16 }}" lowerThreshold="{{50}}" height="{{ 600 }}" overscanCount="{{10}}" item-count="{{ itemCount }}" itemSize="{{ itemSize }}" estimatedItemSize="{{100}}" bind:render="slice" bind:scrolltolower="loadMore">
    <view wx:if="{{styleItems}}">
      <view wx:for="{{ list }}" wx:key="index" style="{{ styleItems[index] }};line-height:50px;border-bottom:1rpx solid #ccc;padding-left:30rpx">{{ item + 1 }}</view>
    </view>
  </virtual-list>
  {{itemCount}}</view>復制代碼
微信小程序中如何實現virtual-list的方法

參考資料

在寫這個微信小程序的virtual-list組件過程中,主要參考了一些優秀的開源虛擬列表實現方案:

  • react-tiny-virtual-list

  • react-virtualized

  • react-window

通過上述解釋已經初步實現了在微信小程序環境中實現了虛擬列表,并且對虛擬列表的原理有了更加深入的了解。但是對于瀑布流布局,列表項尺寸不可預測等場景依然無法適用。在快速滾動過程中,依然會出現來不及渲染而白屏,這個問題可以通過增加「可視區域」外預渲染的item條數overscanCount來得到一定的緩解。

關于“微信小程序中如何實現virtual-list的方法”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

大港区| 虹口区| 东至县| 阿拉善右旗| 湄潭县| 鄂尔多斯市| 武安市| 晋州市| 平顶山市| 高邮市| 唐山市| 光泽县| 高雄县| 绩溪县| 邵阳县| 沙坪坝区| 绥德县| 葫芦岛市| 福清市| 扶沟县| 雷州市| 正宁县| 六枝特区| 金塔县| 台东市| 临夏县| 岫岩| 德保县| 文水县| 阿瓦提县| 聂拉木县| 肇庆市| 永年县| 营山县| 邮箱| 大方县| 崇礼县| 通山县| 临夏县| 新化县| 凤庆县|