需求定義很簡單,原本有一個這樣的瀑布流頁面,滾動加載更多卡片,在此基礎上,增長視頻支持,也就是說多是圖片也多是短視頻。視頻要求在Wi-Fi時內聯靜音自動循環播放,不須要其餘交互。git
是否是很是簡單的一句需求,要求也不高。github
調研了視頻內聯播放的兼容性,問題不大。web
靜音播放也就一個muted屬性的事情。後端
自動播放只須要mounted以後play一下,或者用autoplay屬性。瀏覽器
循環播放就是loop屬性啦。網絡
判斷Wi-Fi環境能夠經過橋與客戶端通訊,這個涉及到具體業務,就不細說了,總之,調個API完事。app
看起來已經實現了。iphone
開始踩坑。ide
按照需求,後端會控制視頻出現的頻率,至少5個卡片才容許出一個視頻,否則滿屏幕視頻,效果很差。但這是一個瀑布流,理論上能夠滾動加載成百上千個卡片,假設每5個卡片放一個視頻,效果會如何呢?函數
大概也就是這樣吧,CPU溫度能夠上90,佔用率300%+(四核八線程),用上那什麼iphone-inline-video插件兼容iOS 9的話,能夠上600%+。
表如今移動端就是滾都滾不動,即便是iPhone X,滑動起來也是很是無比卡頓。
現實世界中,用戶一個屏幕最多看到幾個卡片,基本不可能超過10個,但可能已經加載了上百張卡片,裏面的視頻都還在自動播放,CPU根本吃不消。
因此優化思路是,中止掉不須要的視頻。但個人作法靴微激進了點,參考點評APP首頁的效果,滾到下面再滾回去圖片都是從新加載的,至少看起來是的,也許圖片、視頻資源都被GC了,而不只僅是暫停視頻。
個人實踐是,不在可視區域的圖片和視頻直接替換成空div,用戶滾回來的時候再替換回來。至於圖片會不會被GC,交給容器去作。
那麼,怎麼實現這個效果呢?
一開始我在鑽牛角尖的糾結,如何在父組件list中監聽滾動,判斷屏幕顯示了哪些子組件card,並通知某些子組件你被優化了。
好像不是很好作,那要不在每一個子組件裏本身監聽一下滾動,判斷本身的位置在不在可視區域?感受性能會不好,畢竟滾動事件自己就會觸發不少不少次,還要搞這麼多監聽器。
後來,我參考了lazysizes的實現,它是經過在全局window掛了一個lazyElements列表,在初始化的時候直接querySelectorAll('.custom-class-name')…… 大體思路就是:首先把全部元素加了個特殊的類名,初始化時取出來存在window對象下,而後監聽滾動,forEach直接遍歷全部元素,根據該元素的位置(是否在可視區域內)來判斷是否須要加載。
考慮到咱們頻繁使用的lazysizes也不過就這樣處理的,那我也能夠這麼作吧。主要實現的代碼以下:
export default {
mounted() {
this.addOptimizeScrollListener();
},
beforeDestroy() {
document.removeEventListener('scroll', this._optimizeListener, false);
},
methods: {
addOptimizeScrollListener() {
const windowHeight = window.innerHeight;
const TOLERANCE_HEIGHT = 300;
this._optimizeListener = throttleByRAF(() => {
if (this.$refs.cards) {
this.$refs.cards.forEach(card => {
const rect = card.$el.getBoundingClientRect();
if (rect.top > windowHeight + TOLERANCE_HEIGHT || rect.bottom < 0 - TOLERANCE_HEIGHT) {
card.isOptimized = true;
} else {
card.isOptimized = false;
}
});
}
});
document.addEventListener('scroll', this._optimizeListener, false);
},
}
};
複製代碼
簡單解釋一下:在mounted的時候添加滾動監聽器。監聽器作的事情就是經過$refs
拿到cards組件列表,而後相似lazysizes的操做,直接forEach判斷是否在視窗內,而後給該card組件實例的isOptimized
賦值。這裏有一些優化,好比throttleByRAF用來限流執行,TOLERANCE_HEIGHT用來容錯,不要這麼嚴格的按照視窗邊界來優化,減小用戶輕微滾動致使的重複加載。
主要的實現就是這裏了,card組件內部只須要根據isOptimized
的值來決定渲染圖片視頻仍是空白就能夠了。
值得注意的是,必需要獲取到原有的圖片視頻的高度,不然就會有抖動,甚至瀑布流佈局錯亂。這裏比較特殊的一點是,後端的接口裏提供了寬高,能夠提早預知高度,因此只須要按照給的高度填充空白便可。
其實也能夠把整個card替換成空白佔位(固然我這裏說的空白不是純白色,是五光十色的佔位,若是有想法,還能夠設計一些佔位圖,提升視覺效果)。那怎麼拿到高度呢?
答案就是window.getComputedStyle
! 在mounted的時候獲取一下存起來,被優化的時候用這個高度便可。
首先,來個直觀的體驗對比,那就是CPU佔用沒這麼誇張了,移動端滾起來也很快樂了,用起來和以前僅有圖片時沒有太多差異。
在Performance面板能觀察到Nodes數量大幅減小,未優化時大約16000+,優化後3000~6000個。
因爲以前被誤導多是內存佔用過大致使的,因此我詳細的對比了一下不一樣策略的內存使用狀況。
果真……沒什麼區別。。。能夠看到優化掉整個卡片仍是能省一點內存的。粗略看了一下細節,未優化的狀況下,其中VueComponents的數量爲632,佔用8.9MB,優化後數量僅343個,佔用5.1MB。二者的數量差值爲289,能夠說明當前只渲染11個卡片,符合預期。
然而這點差異相對來講還OK,由於我直接用了線上的圖片300張,每張只有20KB左右。主要仍是解決了CPU佔用太高的問題,而回收DOM帶來的內存優化算是贈送的吧。
在較爲古老的OPPO手機上測試該頁面,發現仍是有些卡頓,嘗試把getNetworkType調用幹掉,就繼續絲滑了。
緣由是每加載一個有視頻的卡片時,我都會去判斷一下網絡類型,以應對用戶切換Wi-Fi到4G之類的操做。由於APP的橋沒有提供相應的監聽函數,只好這樣操做。但顯然,不是很值得。
因而找到了一個在線和離線事件,能夠監聽瀏覽器上線和下線。若是用戶從Wi-Fi切換到4G時會經歷下線和上線,那真是完美了。固然,沒有這麼完美,切換網絡過程當中並無觸發這兩個事件……
最終的解決方案是,每10秒調用一次getNetworkType,用輪詢折衷一下。
靜音播放視頻彷佛沒有問題,可是當我點進一個詳情頁,打開新的webview,再返回到當前頁面時,詭異的播出了聲音…… 而且,尚未找到緣由。
而後發現點評APP首頁用的是WebP格式的動圖,而不是視頻。。。
改方案。。。
改接口。。。
優化基本是白寫了,或者說,能夠優化,但不必。。。
(仍是學到了點東西的)
平臺對WebP支持的稀爛,動圖直接沒處理,暫時沒法支持動圖……
因此仍是要使用視頻。
好消息是這個優化沒白作,壞消息是我必需要解決靜音變成非靜音的bug。
直接提了工單給平臺,得知是對方手滑實現的「特性」,目前能夠經過監聽webview appear事件手動再設置一下muted便可。
這…算是happy ending吧。