圖片視頻瀑布流長列表性能優(yōu)化實踐

先說需求

需求定義很簡單,本來有一個這樣的瀑布流頁面,滾動加載更多卡片,在此基礎(chǔ)上,增加視頻支持,也就是說可能是圖片也可能是短視頻。視頻要求在Wi-Fi時內(nèi)聯(lián)靜音自動循環(huán)播放,不需要其他交互。

是不是非常簡單的一句需求,要求也不高。

瀑布流

調(diào)研了視頻內(nèi)聯(lián)播放的兼容性,問題不大。

靜音播放也就一個muted屬性的事情。

自動播放只需要mounted之后play一下,或者用autoplay屬性。

循環(huán)播放就是loop屬性啦。

判斷Wi-Fi環(huán)境可以通過橋與客戶端通信,這個涉及到具體業(yè)務(wù),就不細說了,總之,調(diào)個API完事。

看起來已經(jīng)實現(xiàn)了。

開始踩坑。

遇到性能問題

按照需求,后端會控制視頻出現(xiàn)的頻率,至少5個卡片才允許出一個視頻,不然滿屏幕視頻,效果不好。但這是一個瀑布流,理論上可以滾動加載成百上千個卡片,假設(shè)每5個卡片放一個視頻,效果會如何呢?

CPU占用情況

大概也就是這樣吧,CPU溫度可以上90,占用率300%+(四核八線程),用上那什么iphone-inline-video插件兼容iOS 9的話,可以上600%+。

表現(xiàn)在移動端就是滾都滾不動,即使是iPhone X,滑動起來也是非常無比卡頓。

嘗試優(yōu)化

現(xiàn)實世界中,用戶一個屏幕最多看到幾個卡片,基本不可能超過10個,但可能已經(jīng)加載了上百張卡片,里面的視頻都還在自動播放,CPU根本吃不消。

所以優(yōu)化思路是,停止掉不需要的視頻。但我的做法靴微激進了點,參考點評APP首頁的效果,滾到下面再滾回去圖片都是重新加載的,至少看起來是的,也許圖片、視頻資源都被GC了,而不僅僅是暫停視頻。

我的實踐是,不在可視區(qū)域的圖片和視頻直接替換成空div,用戶滾回來的時候再替換回來。至于圖片會不會被GC,交給容器去做。

那么,怎么實現(xiàn)這個效果呢?

一開始我在鉆牛角尖的糾結(jié),如何在父組件list中監(jiān)聽滾動,判斷屏幕顯示了哪些子組件card,并通知某些子組件你被優(yōu)化了。

好像不是很好做,那要不在每個子組件里自己監(jiān)聽一下滾動,判斷自己的位置在不在可視區(qū)域?感覺性能會很差,畢竟?jié)L動事件本身就會觸發(fā)很多很多次,還要搞這么多監(jiān)聽器。

后來,我參考了lazysizes的實現(xiàn),它是通過在全局window掛了一個lazyElements列表,在初始化的時候直接querySelectorAll('.custom-class-name')…… 大致思路就是:首先把所有元素加了個特殊的類名,初始化時取出來存在window對象下,然后監(jiān)聽滾動,forEach直接遍歷所有元素,根據(jù)該元素的位置(是否在可視區(qū)域內(nèi))來判斷是否需要加載。

考慮到我們頻繁使用的lazysizes也不過就這樣處理的,那我也可以這么做吧。主要實現(xiàn)的代碼如下:

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的時候添加滾動監(jiān)聽器。監(jiān)聽器做的事情就是通過$refs拿到cards組件列表,然后類似lazysizes的操作,直接forEach判斷是否在視窗內(nèi),然后給該card組件實例的isOptimized賦值。這里有一些優(yōu)化,比如throttleByRAF用來限流執(zhí)行,TOLERANCE_HEIGHT用來容錯,不要這么嚴格的按照視窗邊界來優(yōu)化,減少用戶輕微滾動導(dǎo)致的重復(fù)加載。

主要的實現(xiàn)就是這里了,card組件內(nèi)部只需要根據(jù)isOptimized的值來決定渲染圖片視頻還是空白就可以了。

值得注意的是,必須要獲取到原有的圖片視頻的高度,否則就會有抖動,甚至瀑布流布局錯亂。這里比較特殊的一點是,后端的接口里提供了寬高,可以提前預(yù)知高度,所以只需要按照給的高度填充空白即可。

其實也可以把整個card替換成空白占位(當然我這里說的空白不是純白色,是五顏六色的占位,如果有想法,還可以設(shè)計一些占位圖,提高視覺效果)。那怎么拿到高度呢?

答案就是window.getComputedStyle! 在mounted的時候獲取一下存起來,被優(yōu)化的時候用這個高度即可。

性能對比

首先,來個直觀的體驗對比,那就是CPU占用沒這么夸張了,移動端滾起來也很快樂了,用起來和之前僅有圖片時沒有太多差別。

在Performance面板能觀察到Nodes數(shù)量大幅減少,未優(yōu)化時大約16000+,優(yōu)化后3000~6000個。

由于之前被誤導(dǎo)可能是內(nèi)存占用過大導(dǎo)致的,所以我詳細的對比了一下不同策略的內(nèi)存使用情況。

內(nèi)存快照對比

果然……沒什么區(qū)別。。??梢钥吹絻?yōu)化掉整個卡片還是能省一點內(nèi)存的。粗略看了一下細節(jié),未優(yōu)化的情況下,其中VueComponents的數(shù)量為632,占用8.9MB,優(yōu)化后數(shù)量僅343個,占用5.1MB。兩者的數(shù)量差值為289,可以說明當前只渲染11個卡片,符合預(yù)期。

然而這點差別相對來說還OK,因為我直接用了線上的圖片300張,每張只有20KB左右。主要還是解決了CPU占用過高的問題,而回收DOM帶來的內(nèi)存優(yōu)化算是贈送的吧。

其他問題

判斷Wi-Fi導(dǎo)致卡頓

在較為古老的OPPO手機上測試該頁面,發(fā)現(xiàn)還是有些卡頓,嘗試把getNetworkType調(diào)用干掉,就繼續(xù)絲滑了。

原因是每加載一個有視頻的卡片時,我都會去判斷一下網(wǎng)絡(luò)類型,以應(yīng)對用戶切換Wi-Fi到4G之類的操作。因為APP的橋沒有提供相應(yīng)的監(jiān)聽函數(shù),只好這樣操作。但顯然,不是很值得。

于是找到了一個在線和離線事件,可以監(jiān)聽瀏覽器上線和下線。如果用戶從Wi-Fi切換到4G時會經(jīng)歷下線和上線,那真是完美了。當然,沒有這么完美,切換網(wǎng)絡(luò)過程中并沒有觸發(fā)這兩個事件……

最終的解決方案是,每10秒調(diào)用一次getNetworkType,用輪詢折衷一下。

靜音播放Bug

靜音播放視頻似乎沒有問題,但是當我點進一個詳情頁,打開新的webview,再返回到當前頁面時,詭異的播出了聲音…… 并且,還沒有找到原因。

結(jié)果不做了

然后發(fā)現(xiàn)點評APP首頁用的是WebP格式的動圖,而不是視頻。。。

改方案。。。

改接口。。。

優(yōu)化基本是白寫了,或者說,可以優(yōu)化,但沒必要。。。

(還是學(xué)到了點東西的)

最后的結(jié)局

平臺對WebP支持的稀爛,動圖直接沒處理,暫時無法支持動圖……

所以還是要使用視頻。

好消息是這個優(yōu)化沒白做,壞消息是我必須要解決靜音變成非靜音的bug。

直接提了工單給平臺,得知是對方手滑實現(xiàn)的“特性”,目前可以通過監(jiān)聽webview appear事件手動再設(shè)置一下muted即可。

這…算是happy ending吧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,057評論 4 61
  • 這幾天開始踐行微習(xí)慣,重拾了練字這“行當”。 一開始我是定下了每天練3個字的目標,后來發(fā)覺一行字...
    綿澤先生閱讀 414評論 2 0
  • 20號早上取車后就開始正式的旅程。前一天還晴空萬里的雷市今天一大早就開始下雨,但是坐在車上的我們都還是充滿了信心,...
    坤人指路閱讀 460評論 2 1
  • 想跟疲憊許久的自己談?wù)勑摹?黑夜漫長,我很喜歡,我想不眠,想看盡世間一切繁華,可惜力不從心,我還沒有這個能力,只能...
    村上春樹卍閱讀 296評論 0 0
  • 今天早上,我沒有哼唧,我起床,然后媽媽給我聽寫了語文。我們把時間調(diào)整到早晨讀一門經(jīng)典。很快就讀完啦!然后我們一起去...
    易道而行閱讀 219評論 0 0

友情鏈接更多精彩內(nèi)容