這篇文章將為大家詳細(xì)講解有關(guān)微信小程序中如何實現(xiàn)virtual-list的方法,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
清原ssl適用于網(wǎng)站、小程序/APP、API接口等需要進(jìn)行數(shù)據(jù)傳輸應(yīng)用場景,ssl證書未來市場廣闊!成為成都創(chuàng)新互聯(lián)公司的ssl證書銷售渠道,可以享受市場價格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:028-86922220(備注:SSL證書合作)期待與您的合作!
小程序在很多場景下面會遇到長列表的交互,當(dāng)一個頁面渲染過多的wxml節(jié)點的時候,會造成小程序頁面的卡頓和白屏。原因主要有以下幾點:
1.列表數(shù)據(jù)量大,初始化setData和初始化渲染列表wxml耗時都比較長;
2.渲染的wxml節(jié)點比較多,每次setData更新視圖都需要創(chuàng)建新的虛擬樹,和舊樹的diff操作耗時比較高;
3.渲染的wxml節(jié)點比較多,page能夠容納的wxml是有限的,占用的內(nèi)存高。
微信小程序本身的scroll-view沒有針對長列表做優(yōu)化,官方組件recycle-view就是一個類似virtual-list的長列表組件。現(xiàn)在我們要剖析虛擬列表的原理,從零實現(xiàn)一個小程序的virtual-list。
首先我們要了解什么是virtual-list,這是一種初始化只加載「可視區(qū)域」及其附近dom元素,并且在滾動過程中通過復(fù)用dom元素只渲染「可視區(qū)域」及其附近dom元素的滾動列表前端優(yōu)化技術(shù)。相比傳統(tǒng)的列表方式可以到達(dá)極高的初次渲染性能,并且在滾動過程中只維持超輕量的dom結(jié)構(gòu)。
虛擬列表最重要的幾個概念:
可滾動區(qū)域:比如列表容器的高度是600,內(nèi)部元素的高度之和超過了容器高度,這一塊區(qū)域就可以滾動,就是「可滾動區(qū)域」;
可視區(qū)域:比如列表容器的高度是600,右側(cè)有縱向滾動條可以滾動,視覺可見的內(nèi)部區(qū)域就是「可視區(qū)域」。
實現(xiàn)虛擬列表的核心就是監(jiān)聽scroll事件,通過滾動距離offset和滾動的元素的尺寸之和totalSize動態(tài)調(diào)整「可視區(qū)域」數(shù)據(jù)渲染的頂部距離和前后截取索引值,實現(xiàn)步驟如下:
1.監(jiān)聽scroll事件的scrollTop/scrollLeft,計算「可視區(qū)域」起始項的索引值startIndex和結(jié)束項索引值endIndex;
2.通過startIndex和endIndex截取長列表的「可視區(qū)域」的數(shù)據(jù)項,更新到列表中;
3.計算可滾動區(qū)域的高度和item的偏移量,并應(yīng)用在可滾動區(qū)域和item上。

在虛擬列表中,依賴每一個列表項的寬/高來計算「可滾動區(qū)域」,而且可能是需要自定義的,定義itemSizeGetter函數(shù)來計算列表項寬/高。
itemSizeGetter(itemSize) { return (index: number) => { if (isFunction(itemSize)) { return itemSize(index);
} return isArray(itemSize) ? itemSize[index] : itemSize;
};
}復(fù)制代碼滾動過程中,不會計算沒有出現(xiàn)過的列表項的itemSize,這個時候會使用一個預(yù)估的列表項estimatedItemSize,目的就是在計算「可滾動區(qū)域」高度的時候,沒有測量過的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
);
}復(fù)制代碼這里看到了是直接通過緩存命中最近一個計算過的列表項的itemSize和offset,這是因為在獲取每一個列表項的兩個參數(shù)時候,都對其做了緩存。
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];
}復(fù)制代碼在滾動過程中,需要通過滾動偏移量offset計算出展示在「可視區(qū)域」首項數(shù)據(jù)的索引值,一般情況下可以從0開始計算每一列表項的itemSize,累加到一旦超過offset,就可以得到這個索引值。但是在數(shù)據(jù)量太大和頻繁觸發(fā)的滾動事件中,會有較大的性能損耗。好在列表項的滾動距離是完全升序排列的,所以可以對已經(jīng)緩存的數(shù)據(jù)做二分查找,把時間復(fù)雜度降低到 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;
}復(fù)制代碼對于搜索沒有緩存計算結(jié)果的查找,先使用指數(shù)查找縮小查找范圍,再使用二分查找。
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,
});
}
}復(fù)制代碼我們知道了「可視區(qū)域」尺寸containerSize,滾動偏移量offset,在加上預(yù)渲染的條數(shù)overscanCount進(jìn)行調(diào)整,就可以計算出「可視區(qū)域」起始項的索引值startIndex和結(jié)束項索引值endIndex,實現(xiàn)步驟如下:
1.找到距離offset最近的索引值,這個值就是起始項的索引值startIndex;
2.通過startIndex獲取此項的offset和size,再對offset進(jìn)行調(diào)整;
3.offset加上containerSize得到結(jié)束項的maxOffset,從startIndex開始累加,直到越過maxOffset,得到結(jié)束項索引值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,
};
}復(fù)制代碼現(xiàn)在可以通過監(jiān)聽scroll事件,動態(tài)更新startIndex、endIndex、totalSize、offset,就可以實現(xiàn)虛擬列表滾動。
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;
},復(fù)制代碼在調(diào)用的時候,通過render事件回調(diào)出來的startIndex, stopIndex,styleItems,截取長列表「可視區(qū)域」的數(shù)據(jù),在把列表項目的itemSize和offset通過絕對定位的方式應(yīng)用在列表上
代碼如下:
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);
},
});復(fù)制代碼<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>復(fù)制代碼
在寫這個微信小程序的virtual-list組件過程中,主要參考了一些優(yōu)秀的開源虛擬列表實現(xiàn)方案:
react-tiny-virtual-list
react-virtualized
react-window
通過上述解釋已經(jīng)初步實現(xiàn)了在微信小程序環(huán)境中實現(xiàn)了虛擬列表,并且對虛擬列表的原理有了更加深入的了解。但是對于瀑布流布局,列表項尺寸不可預(yù)測等場景依然無法適用。在快速滾動過程中,依然會出現(xiàn)來不及渲染而白屏,這個問題可以通過增加「可視區(qū)域」外預(yù)渲染的item條數(shù)overscanCount來得到一定的緩解。
關(guān)于“微信小程序中如何實現(xiàn)virtual-list的方法”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,使各位可以學(xué)到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
網(wǎng)站名稱:微信小程序中如何實現(xiàn)virtual-list的方法
標(biāo)題網(wǎng)址:http://chinadenli.net/article16/gdssgg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站營銷、建站公司、動態(tài)網(wǎng)站、云服務(wù)器、網(wǎng)頁設(shè)計公司、企業(yè)網(wǎng)站制作
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)