小程序多圖列表實(shí)現(xiàn)性能優(yōu)化的方法步驟
寫這篇文章的緣由: 最近在公司的小程序項(xiàng)目中遇到了頁(yè)面圖片元素過多導(dǎo)致的性能問題. 從小程序提供的性能檢測(cè)面板分析, 確定是圖片元素占用了過多內(nèi)存導(dǎo)致.
因?yàn)楸救酥爸饕亲鲎烂娑藨?yīng)用開發(fā)和原生app開發(fā), 沒有太顧及過移動(dòng)端圖片的內(nèi)存占用問題. 這次既然遇到了, 也就趁這個(gè)機(jī)會(huì)學(xué)習(xí)一下其優(yōu)化的技巧.
什么造成的性能問題
簡(jiǎn)單的來說: DOM節(jié)點(diǎn)過多 && 圖片節(jié)點(diǎn)過多
DOM節(jié)點(diǎn)過多會(huì)造成更多的內(nèi)存占用. 按照目前的微信小程序限制, 內(nèi)存占用500M以上會(huì)出現(xiàn)卡頓, 甚至閃退. 如果列表中節(jié)點(diǎn)沒有圖片標(biāo)簽, 內(nèi)存占用現(xiàn)象還不會(huì)太明顯, 只是DOM節(jié)點(diǎn)過多會(huì)造成頁(yè)面渲染耗時(shí)增加. 但當(dāng)列表節(jié)點(diǎn)中含有圖片時(shí), 內(nèi)存占用會(huì)迅速攀升.
如何解決這兩點(diǎn)呢?
對(duì)于上面兩點(diǎn), 我們分別有對(duì)應(yīng)的優(yōu)化思路
1. DOM節(jié)點(diǎn)過多.
對(duì)于無限加載的頁(yè)面, 列表中每一個(gè)元素都有大量的子節(jié)點(diǎn). 當(dāng)列表數(shù)目增加時(shí), 頁(yè)面的總節(jié)點(diǎn)數(shù)會(huì)暴增. 以小紅書的小程序?yàn)槔?

上圖中可以看到, 該頁(yè)面為很多的卡片組成的列表頁(yè)面. 假設(shè)一個(gè)卡片的DOM子節(jié)點(diǎn)數(shù)為 30, 那么當(dāng)列表元素加載到1000時(shí), 頁(yè)面會(huì)有 30 * 1000 = 30,000 個(gè)DOM節(jié)點(diǎn). 小程序顯然是吃不消的 (注: 微信小程序推薦總節(jié)點(diǎn)數(shù)不超過: 1000)
那我們?cè)撊绾翁幚韥頊p少節(jié)點(diǎn)數(shù)呢?
思路很簡(jiǎn)單: 我們可以只對(duì)用戶當(dāng)前屏幕和上下兩屏進(jìn)行真實(shí)內(nèi)容的加載, 對(duì)于其他用戶暫時(shí)不可見的地方, 用空白的節(jié)點(diǎn)進(jìn)行占位. 這樣處理后, 實(shí)際有內(nèi)容的卡片數(shù)目不超過5個(gè), 頁(yè)面的總節(jié)點(diǎn)數(shù)為: 5 * 30 + 995 = 1145. 相對(duì)于之前的節(jié)點(diǎn)數(shù)有了巨大的改進(jìn).
讓我們來看看代碼的實(shí)現(xiàn)
寫代碼前, 讓我們整理一下需要的數(shù)據(jù)結(jié)構(gòu).
首先這是一個(gè)列表頁(yè)面, 我們需要一個(gè) List來保存頁(yè)面顯示的數(shù)據(jù): showCards. showCards 中只會(huì)保存5條真實(shí)數(shù)據(jù), 其余數(shù)據(jù)展示以空對(duì)象填充.
我們還需要一個(gè)保存所有真實(shí)數(shù)據(jù)的List, 這樣當(dāng)用戶滑動(dòng)頁(yè)面時(shí), 我們才能實(shí)時(shí)獲取需要顯示的卡片真實(shí)數(shù)據(jù): totalCards
Page({
showCards: [],
totalCards: []
})
接下來我們來寫頁(yè)面布局部分:
<view wx:for="{{showCards}}"
wx:key="{{index}}">
<self-define-component data-card-data="{{item}}">
</self-define-component>
</view>
簡(jiǎn)單的代碼框架就是這樣 (這里省略了很多不影響理解思路的代碼細(xì)節(jié))
我們先實(shí)現(xiàn)沒有優(yōu)化DOM節(jié)點(diǎn)的代碼邏輯. 在頁(yè)面滑動(dòng)到最底部時(shí), 向showCards push進(jìn)新的卡片, 并通過 setData 更新頁(yè)面. 這樣就實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的下拉無限加載的列表頁(yè)面.
async onReachBottom() {
const newCards = await fetchNewCards();
this.data.showCards.push(newCards);
this.setData({
showCards: this.data.showCards
})
},
接下來我們實(shí)現(xiàn)優(yōu)化DOM節(jié)點(diǎn)的代碼邏輯. 我們會(huì)再用戶滑動(dòng)頁(yè)面(onScroll事件) 時(shí), 對(duì)當(dāng)前頁(yè)面每個(gè)card 的位置進(jìn)行判斷, 如果該 card在用戶可見范圍內(nèi)的上下兩屏內(nèi), 則展示真實(shí)數(shù)據(jù), 否則將其替換為寬高與原卡片一致的空白占位節(jié)點(diǎn).
在 Page 的 onPageScroll 回調(diào)中, 我們進(jìn)行回收函數(shù)的調(diào)用 (注意這里回調(diào)時(shí)要進(jìn)行節(jié)流處理, 否則頻繁調(diào)用會(huì)導(dǎo)致頁(yè)面閃動(dòng)) . 讓我們看看這個(gè)回收頁(yè)面節(jié)點(diǎn)函數(shù)的主要邏輯:
回調(diào)中, 我們首先通過小程序提供的獲取頁(yè)面元素位置的api: createSelectorQuery().boundingClientRect 來拿到每個(gè)卡片的位置信息.
接下來, 我們通過位置信息, 判斷是否展示card的真實(shí)數(shù)據(jù). 對(duì)于不展示真實(shí)數(shù)據(jù)的card, 我們需要保存其高度信息, 以便在渲染頁(yè)面時(shí)使用高度信息填充頁(yè)面. 同時(shí)我們給空card一個(gè) type 屬性, 方便我們?cè)?wxml中渲染時(shí)判斷卡片類型.
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
});
}
接下來, 我們要對(duì)wxml布局文件進(jìn)行相應(yīng)的修改:
<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節(jié)點(diǎn)數(shù)目過多的問題. 并且最大限度的不影響用戶的體驗(yàn). (雖然用戶快速上下滑動(dòng)時(shí)還是會(huì)看到一些空白, 但大多數(shù)情況用戶不會(huì)非??焖俚纳舷禄? 而是閱讀內(nèi)容并慢速滑動(dòng))
2. 圖片節(jié)點(diǎn)過多
通過上面一步的優(yōu)化, 我們其實(shí)已經(jīng)大幅減少了頁(yè)面加載的圖片數(shù)目. 但是有些情況, 我們的列表中的每一個(gè)卡片并不是只有一張圖, 有時(shí)我們的圖片組件是一個(gè) swiper. 我們假設(shè)每個(gè)swiper平均展示10張圖片, 那么我們展示5張card的話,<Image/> 節(jié)點(diǎn)就有 50 個(gè). 對(duì)于一些低端的安卓機(jī), 這樣的開銷依然會(huì)造成卡頓.
那有什么好的優(yōu)化方案呢? 前面一個(gè)問題, 我們的優(yōu)化思路是在用戶看不見的地方, 將節(jié)點(diǎn)簡(jiǎn)化展示.
同樣的, 對(duì)于swiper控件, 用戶能看到的也就是當(dāng)前圖片 以及 滑動(dòng)可見的左右兩張圖片. 其余位置的圖片是可以簡(jiǎn)化展示的. 從下圖可以看到, 其實(shí)需要立即加載的圖片只有三張. (紅色的框代表的是swiper組件的可視區(qū)域)

我們使用一個(gè)變量記錄當(dāng)前swiper控件展示圖片的坐標(biāo): curIndex, 然后我們改造一下 wxml布局文件. 代碼邏輯很簡(jiǎn)單, 就是通過判斷當(dāng)前Image 節(jié)點(diǎn)的index和swiper展示節(jié)點(diǎn)的 index之間距離, 大于2就不顯示.
經(jīng)過這樣的處理后, 我們的每個(gè)swiper組件, 最多只會(huì)有三個(gè)占用實(shí)際內(nèi)存的 <Image/> 節(jié)點(diǎn).
<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>
最后
以上就是我在這次性能優(yōu)化中用到的一些小技巧, 希望能為你帶來一些幫助 :)
如果你對(duì)我的文章感興趣, 這里有我的一些 數(shù)據(jù)可視化, D3.js 方面的文章, 歡迎 fork && star:
https://github.com/ssthouse/ssthouse-blog
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
實(shí)現(xiàn)png圖片和png背景透明(支持多瀏覽器)的方法
Firefox和Opera對(duì)PNG的支持非常的好,都是IE卻無視PNG圖片這一特性的“存在”,雖然IE7已經(jīng)支持都是IE6還是不行。2009-09-09
用js實(shí)現(xiàn)多域名不同文件的調(diào)用方法
用js實(shí)現(xiàn)多域名不同文件的調(diào)用方法...2007-01-01
淺談JavaScript 中有關(guān)時(shí)間對(duì)象的方法
下面小編就為大家?guī)硪黄獪\談JavaScript 中有關(guān)時(shí)間對(duì)象的方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-08-08
javascript驗(yàn)證只能輸入數(shù)字和一個(gè)小數(shù)點(diǎn)示例
使用javascript限制只能輸入數(shù)字和一個(gè)小數(shù)點(diǎn),在某些情況下還是比較使用的,下面有個(gè)不錯(cuò)的示例,感興趣的朋友可以參考下2013-10-10
JS實(shí)現(xiàn)簡(jiǎn)單的圖書館享元模式實(shí)例
這篇文章主要介紹了JS實(shí)現(xiàn)簡(jiǎn)單的圖書館享元模式,以一個(gè)圖書館存書借書的例子分析了圖書館享元模式的實(shí)現(xiàn)技巧,需要的朋友可以參考下2015-06-06
Bootstrap基本樣式學(xué)習(xí)筆記之標(biāo)簽(5)
這篇文章主要介紹了Bootstrap學(xué)習(xí)筆記之標(biāo)簽基本樣式的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12
原生Js與jquery的多組處理, 僅展開一個(gè)區(qū)塊的折疊效果
同一個(gè)頁(yè)面, 有多組(不固定), 每組區(qū)塊數(shù)量不一定一樣的小區(qū)塊. 要求每次只展開一個(gè)區(qū)塊,需要的朋友可以參考下。2011-01-01
js AppendChild與insertBefore用法詳細(xì)對(duì)比
本篇文章主要是對(duì)js中AppendChild與insertBefore的用法進(jìn)行了詳細(xì)的對(duì)比。需要的朋友可以過來參考下,希望對(duì)大家有所幫助2013-12-12

