原文鏈接
傳送門
需求
最近幾天在忙著搞公司專案的一個新的需求,原因是這樣的:公司準備開發一個偏向于社交娛樂項的小程式,其中首頁是可以看到用戶發的話題帖子之類的,每個帖子都至少包含一張圖片或者一個視頻, 然后產品那邊希望首頁可以實作instagram的互動效果,效果圖如下:

嗯,大致上這個就是需求的背景,然后就是每個帖子的高度是不確定的,高度大概在500~600px之間,
實作思路
一開始接到這個需求,其實我心里還是有點慌的,畢竟有一段時間不怎么接觸小程式,也不知道小程式更新到什么程度,檔案更新到什么程度,仔細分析一下專案需求,大致上可以歸類為兩個:互動 和 性能優化,
性能優化
因為首頁是一個長串列,眾所周知,頁面一旦渲染的節點過多,就會卡頓,更何況是小程式,并且小程式是分為邏輯層和渲染層,兩者通過setData鏈接,所以處理的時候需要注意兩點: 1. setData的資料量不能太大,記得好像是有個大小限制,,忘了是多少,你們可以自己在官方檔案上找一下; 2. 頁面能夠渲染的帖子數量是有限的,在這里,我是控制為最多渲染25個帖子,
針對于長串列的優化,官方也有相應的組件-[recycle-view](https://github.com/wechat-miniprogram/recycle-view),但是貌似并不符合專案需求,所以被我pass掉了, 雖然沒用官方的組件,但是在組件的檔案里面把對于長串列得性能優化解釋一遍,這里摘抄一下重點: >? 核心的思路就是只渲染顯示在螢屏的資料,基本實作就是監聽 scroll 事件,并且重新計算需要渲染的資料,不需要渲染的資料留一個空的 div 占位元素, 其實也就是設定一個變數控制該資料是否可以渲染,如果是不能夠渲染得話,那我們就用一個空的view取代它,需要注意的失敗的是:空的框架高度需要設定為帖子的高度,這樣子才不會閃屏, 針對這種思路,我們就可以確定其中一種長串列的性能優化的解決思路: 1. 將資料分為二維陣列,這樣子就可以限制每次setData的資料量,等待資料渲染完成之后,獲取每組資料所占用的總高度,這里的高度是為了在改組資料不渲染時設定占位框的高度,
/**
* 獲取 有渲染,但是高度還沒獲取到的分組 的高度
*/
_getGroupListHeight() {
this.data.list.forEach((item, index) => {
if (item.show && !item.height) {
const id = 'XXXXXXXX' // 組的id
let query = wx.createSelectorQuery()
query.select(id).boundingClientRect(rect => {
this.data.list[index].height = rect.height
}).exec();
this.getTopicHeight(item.data, index) // 獲取串列中每個話題的高度,用于計算滑動時要滾動的距離
}
})
}
上面就是一個簡單獲取每組的高度的代碼實體,當該組資料有被渲染但是高度不明的情況下,就會去獲取,加一步判斷是為了防止重獲獲取組的資料,造成不必要的浪費,在獲取每組資料的高度時,還會對應去 獲取該組的每個帖子的高度,這樣子是為了后面實作 仿instagram 互動做準備, 2. 獲取到了每組資料的高度,接下來,我們就可以監聽頁面滾動的高度,從而控制需要渲染的資料,需要注意的一點是,我們需要在該組的資料基礎上,多渲染上兩組和下兩組資料,目的是防止用戶快速滑動的時候出現白屏的不友好體驗,當然也可以根據自己的需要多渲染幾組,
/**
* 頁面滾動
* @param e
*/
onPageScroll(e) {
// android頁面滑動處理(非仿instagram版本)
if (!this.data.isIos && !this.data.scrollBoxInfo.canUseScrollBox) {
// 1. 處理當前頁面正在播放的視頻
if (this.data.currentPlayingId && Math.abs(e.scrollTop - this.data.scrollTop) > 100) {
this.selectComponent(this.data.currentPlayingId).pauseVideo()
this.data.currentPlayingId = ''
}
// 2. 處理 Andorid 渲染的分組資料
this._dealAndroidScroll(e)
// 3. 處理視頻自動播放
if (this.timer) {
clearTimeout(this.timer)
}
this.timer = setTimeout(() => {
this.data.scrollTop = e.scrollTop // 記錄下當前的滾動距離,滑動暫停視頻播放的時候需要用到
this.handleAutoPlay(e) // 視頻自動播放
}, 300)
}
}
/**
* Android 監聽滾動,動態設定分組
* @param {Object} e
*/
_dealAndroidScroll(e) {
let max_height = 0 // 最大高度
for (let i = 0; i <= this.data.topicScroll.show_index; i++) {
max_height += this.data.list[i].height
}
let min_height = max_height - this.data.list[this.data.topicScroll.show_index].height // 最小高度
// 超過,+1
if (e.scrollTop > max_height && this.data.topicScroll.show_index < this.data.list.length - 1) {
++this.data.topicScroll.show_index
this._dealListShow(this.data.topicScroll.show_index)
}
// 小于,-1
if (e.scrollTop < min_height) {
--this.data.topicScroll.show_index
this._dealListShow(this.data.topicScroll.show_index)
}
}
這里沒有對代碼進行過濾,代碼實作的效果就是監聽滾動到的位置,并且設定渲染分組資料,因為有些是視頻帖子,所以需要在滾動完成之后實作自動播放視頻的功能,這段代碼只是處理anroid平滑滾動的情況,因為代碼涉及到 instagram 互動的實作,
對于長串列性能優化的思路大致上就這樣:資料分為二位陣列,渲染指定分組的資料,減少渲染的資料量和需要渲染的節點數量, 不需要渲染的資料就用指定高度的空的view替代,指定高度是為了防止閃屏,
仿instagram互動實作
從上面的 instagram 互動視頻可以看出來,我們需要監聽用戶的手勢滑動從而控制帖子的切換,并且每次切換只是切換一個帖子,知道了互動的詳情,我們就可以展開想象了,大致上可以給個基本的實作思路: 1. 監聽手指點擊和手指離開的事件,記錄下手指點擊的高度 && 手指離開的高度,用來判斷用戶滑動的距離和方向;記錄下手指點擊 和 手指離開的時間,可以粗略用來判斷用戶當前的滑動行為是快滑還是慢滑,根據上面的得到的資訊,我們基本上實作滑動切換帖子的操作:
/**
* 監聽手指點擊操作
* @param e
*/
touchStart(e) {
this.data.topicScroll.startTimeStamp = new Date().getTime() // 記錄下當前手指點擊事件
this.data.topicScroll.startPosition = e.changedTouches[0].clientY // 記錄下手指開始點擊的位置
},
/**
* 手指離開螢屏
* @param e
*/
touchEnd(e) {
const diffTime = new Date().getTime() - this.data.topicScroll.startTimeStamp // 手指離開的時候的時間戳
const clientY = e.changedTouches[0].clientY // 手指離開螢屏的位置
const diffY = Math.abs(clientY - this.data.topicScroll.startPosition) // 手指滑動的距離
const direction = this.data.topicScroll.startPosition - clientY > 0 // 手指滑動的方向,true為向上滑,false為向下滑
const scrollInfo = this.data.topicScroll
// 1. 第一個節點手指向下滑動 && 最后一個節點手指向上滑動 不做操作
if (!scrollInfo.parent_index && !scrollInfo.child_index && !direction) {
return
} // 第一個節點向下滑動不做操作
if (scrollInfo.parent_index === (this.data.list.length - 1) && scrollInfo.child_index === (this.data.list[scrollInfo.parent_index].data.length - 1) && direction) {
return
} // 最后一個節點向上滑動不做操作
// 2. 根據滑動的方向,判斷需要滾動到哪個節點下
const can_move = (diffTime < 100 && diffY > 50) || (diffTime >= 100 && diffY > 80) // 是否可以滑動,手勢滑動判斷依據
if (can_move) {
if (direction) {
if (scrollInfo.child_index === 4) {
++scrollInfo.parent_index
scrollInfo.child_index = 0
} else {
++scrollInfo.child_index
}
} else {
if (scrollInfo.child_index === 0) {
--scrollInfo.parent_index
scrollInfo.child_index = 4
} else {
--scrollInfo.child_index
}
}
}
// 3. 處理滾動
this._dealScroll(can_move ? pausePlayId : '', can_move)
}
根據監聽到資訊,可以判斷是否切換以及切換到那個帖子的操作,所以我們只需要根據面已經獲取到的帖子高度就可以計算出來要滾動的高度, 2. 根據第一點的操作,我們就可以實作 仿Instagram 互動效果,但是忽略了致命的一點:頁面的慣性滾動,也正是因為這個所謂的“慣性滾動”,我多花了幾天的時間去研究互動的實作:boom: 眾所周知,為了使得頁面的滑動更加流暢,當我們滑動停止的時候,頁面就像會產生慣性一般,自動的滑動一定距離才停下,
安卓下默認有慣性滾動,而在 iOS 下需要額外設定-webkit-overflow-scrolling: touch的樣式
而第一點方案實作的大前提是頁面不能擁有慣性滾動,否則的話頁面無法準確滾動到指定位置,
解決思路
1. ios的解決方案比較簡單,我們只需要設定 -webkit-overflow-scrolling: auto 的樣式即可, 2. 比較麻煩的是android的實作,一開始我是上網找了不少的資料去實作取消頁面的慣性滾動,畢竟頁面滾動的性能是最好的,不過很可惜,我沒有找到可行的方案,所以我選擇退而求其次,模擬手指的滑動,經過對小程式檔案的瀏覽,我選擇可兩種比較可能實作的方案:①使用wxs實作手指滑動的效果;②使用scroll-view的fast-deceleration 屬性,顯而易見,使用wxs方案實作比較復雜,所以我一開始選擇scroll-view這個方案,
scroll-view方案實作的思路與ios類似,比較不同的是頁面的滾動,因為ios是可以直接使用頁面滾動的,即wx.pageScrollTo,而scroll-view則是使用scroll-into-view 或者 scroll-top,為了減少操作,我是直接使用了scroll-top這個屬性,正當我信心滿滿的時候,做出來的效果卻是打了我的臉:每次滾動到指定位置的時候都會抖動一下,雖然沒有仔細去查明原因,不過
我猜想是頁面滾動依然存在一定的慣性滾動,當我們設定了scroll-top的時候,慣性滾動的存在會使得串列偏移位置,最后在偏移回來,這樣子看起來就像是抖動了一下,
正當我準備放棄scroll-view這個方案的時候,無意中讓我發現了scroll-view的api介面,本著想試一下新介面的心態,我嘗試一下使用官方的api介面控制scroll-view滾動,神奇的是,成了!!!!
既然scroll-view做出來的效果勉強還可以,我就直接pass后面的wxs方案,畢竟那一塊的邏輯應該會比較復雜,
因為時使用了scroll-view的api介面,支持的版本庫比較高,是2.14.4,雖然大部分的用戶可以支持,但還是要兼容少部分的用戶,所以android部分我是搞了一個scroll-view版本和平滑滾動版本 fast-deceleration這個特性在開發者工具那里貌
demo
說了這么多,也不一定有人看得下去,demo呈上
結尾
在除錯這個效果的時候,遇到不少的坑,其中有一個坑印象巨深,在此記錄一下:
touch 期間 touchstart 的目標節點被移除,則對應的 touchend 事件會因為沒有目標節點而缺失,
遇到這個坑是因為小程式同個頁面不允許存在多個視頻,所以需要將沒有播放的視頻使用圖片替換,需要播放的視頻的時候就替換回來,這樣子,就會出現上面的情況,導致無法監聽到touchend事件,整個串列停在原地,沒有滾動到指定位置,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/352254.html
標籤:其他
