寫這篇文章的原因: 最近在公司的小程序項目中遇到了頁面圖片元素過多致使的性能問題. 從小程序提供的性能檢測面板分析, 肯定是圖片元素佔用了過多內存致使.javascript
由於本人以前主要是作桌面端應用開發和原生app開發, 沒有太顧及過移動端圖片的內存佔用問題. 此次既然遇到了, 也就趁這個機會學習一下其優化的技巧.java
簡單的來講: DOM節點過多 && 圖片節點過多git
DOM節點過多會形成更多的內存佔用. 按照目前的微信小程序限制, 內存佔用500M以上會出現卡頓, 甚至閃退. 若是列表中節點沒有圖片標籤, 內存佔用現象還不會太明顯, 只是DOM節點過多會形成頁面渲染耗時增長. 但當列表節點中含有圖片時, 內存佔用會迅速攀升.github
對於上面兩點, 咱們分別有對應的優化思路小程序
對於無限加載的頁面, 列表中每個元素都有大量的子節點. 當列表數目增長時, 頁面的總節點數會暴增. 以小紅書的小程序爲例:微信小程序
上圖中能夠看到, 該頁面爲不少的卡片組成的列表頁面. 假設一個卡片的DOM子節點數爲 30, 那麼當列表元素加載到1000時, 頁面會有 30 * 1000 = 30,000
個DOM節點. 小程序顯然是吃不消的 (注: 微信小程序推薦總節點數不超過: 1000)api
那咱們該如何處理來減小節點數呢?性能優化
思路很簡單: 咱們能夠只對用戶當前屏幕和上下兩屏進行真實內容的加載, 對於其餘用戶暫時不可見的地方, 用空白的節點進行佔位. 這樣處理後, 實際有內容的卡片數目不超過5個, 頁面的總節點數爲: 5 * 30 + 995 = 1145
. 相對於以前的節點數有了巨大的改進.bash
寫代碼前, 讓咱們整理一下須要的數據結構.微信
首先這是一個列表頁面, 咱們須要一個 List來保存頁面顯示的數據: showCards
. showCards
中只會保存5條真實數據, 其他數據展現以空對象填充.
咱們還須要一個保存全部真實數據的List, 這樣當用戶滑動頁面時, 咱們才能實時獲取須要顯示的卡片真實數據: totalCards
Page({
showCards: [],
totalCards: []
})
複製代碼
接下來咱們來寫頁面佈局部分:
<view wx:for="{{showCards}}"
wx:key="{{index}}">
<self-define-component data-card-data="{{item}}">
</self-define-component>
</view>
複製代碼
簡單的代碼框架就是這樣 (這裏省略了不少不影響理解思路的代碼細節)
咱們先實現沒有優化DOM節點的代碼邏輯. 在頁面滑動到最底部時, 向showCards
push進新的卡片, 並經過 setData
更新頁面. 這樣就實現了一個簡單的下拉無限加載的列表頁面.
async onReachBottom() {
const newCards = await fetchNewCards();
this.data.showCards.push(newCards);
this.setData({
showCards: this.data.showCards
})
},
複製代碼
接下來咱們實現優化DOM節點的代碼邏輯. 咱們會再用戶滑動頁面(onScroll
事件) 時, 對當前頁面每一個card
的位置進行判斷, 若是該 card在用戶可見範圍內的上下兩屏內, 則展現真實數據, 不然將其替換爲寬高與原卡片一致的空白佔位節點.
在 Page 的 onPageScroll
回調中, 咱們進行回收函數的調用 (注意這裏回調時要進行節流處理, 不然頻繁調用會致使頁面閃動) . 讓咱們看看這個回收頁面節點函數的主要邏輯:
回調中, 咱們首先經過小程序提供的獲取頁面元素位置的api: createSelectorQuery().boundingClientRect
來拿到每一個卡片的位置信息.
接下來, 咱們經過位置信息, 判斷是否展現card的真實數據. 對於不展現真實數據的card, 咱們須要保存其高度信息, 以便在渲染頁面時使用高度信息填充頁面. 同時咱們給空card一個 type 屬性, 方便咱們在 wxml中渲染時判斷卡片類型.
async onScrollCallback() {
try {
const rectList = await this.calcCardsHeight();
this.recycleCard(rectList);
} catch (e) {
console.error(e);
}
}
calcFeedHeight() {
return new Promise((resolve, reject) => {
this.createSelectorQuery()
.selectAll(`.card`)
.boundingClientRect(rectList => {
resolve(rectList);
})
.exec()
})
},
recycleCard(rectList) {
const newShowCards = [];
for (let i = 0; i < this.data.showCards.length; i++) {
const rect = rectList[i];
if (rect && Math.abs(rectList[i].top - 0) > pageHeight * 2) {
newShowCards.push({
type: 'empty-card',
height: rectList[i].bottom - rectList[i].top
});
} else {
const feed = totalCards[i];
newShowCards.push(feed);
}
}
this.setData({
showCards: newShowCards
});
}
複製代碼
接下來, 咱們要對wxml佈局文件進行相應的修改:
<view wx:for="{{showCards}}"
wx:key="{{index}}">
<view wx:if="{{item.type === 'empty-card'}}"
class="card empty-card"
style="height: {{item.height}}px">
</view>
<self-define-component wx:if="{{item.type !== 'empty-card'}}"
data-card-data="{{item}}"
class="card read-card">
</self-define-component>
</view>
複製代碼
這樣, 咱們就解決了 DOM節點數目過多的問題. 而且最大限度的不影響用戶的體驗. (雖然用戶快速上下滑動時仍是會看到一些空白, 但大多數狀況用戶不會很是快速的上下滑, 而是閱讀內容並慢速滑動)
經過上面一步的優化, 咱們其實已經大幅減小了頁面加載的圖片數目. 可是有些狀況, 咱們的列表中的每個卡片並非只有一張圖, 有時咱們的圖片組件是一個 swiper. 咱們假設每一個swiper平均展現10張圖片, 那麼咱們展現5張card的話,<Image/>
節點就有 50 個. 對於一些低端的安卓機, 這樣的開銷依然會形成卡頓.
那有什麼好的優化方案呢? 前面一個問題, 咱們的優化思路是在用戶看不見的地方, 將節點簡化展現.
一樣的, 對於swiper控件, 用戶能看到的也就是當前圖片 以及 滑動可見的左右兩張圖片. 其他位置的圖片是能夠簡化展現的. 從下圖能夠看到, 其實須要當即加載的圖片只有三張. (紅色的框表明的是swiper組件的可視區域)
咱們使用一個變量記錄當前swiper控件展現圖片的座標: curIndex
, 而後咱們改造一下 wxml佈局文件. 代碼邏輯很簡單, 就是經過判斷當前Image 節點的index和swiper展現節點的 index之間距離, 大於2就不顯示.
通過這樣的處理後, 咱們的每一個swiper組件, 最多隻會有三個佔用實際內存的 <Image/>
節點.
<swiper-item wx:for="{{images}}"
wx:key="{{index}}">
<view >
<image class="img"
mode="widthFix"
src="{{index - curIndex < 2 && index - curIndex > -2 ? item.url : ''}}">
</image>
</view>
</swiper-item>
複製代碼
以上就是我在此次性能優化中用到的一些小技巧, 但願能爲你帶來一些幫助 :)
若是你對個人文章感興趣, 這裏有個人一些 數據可視化, D3.js 方面的文章, 歡迎 fork && star: