图片视频瀑布流长列表性能优化实践

先说需求

需求定义很简单,本来有一个这样的瀑布流页面,滚动加载更多卡片,在此基础上,增加视频支持,也就是说可能是图片也可能是短视频。视频要求在Wi-Fi时内联静音自动循环播放,不需要其他交互。

是不是非常简单的一句需求,要求也不高。

瀑布流

调研了视频内联播放的兼容性,问题不大。

静音播放也就一个muted属性的事情。

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

循环播放就是loop属性啦。

判断Wi-Fi环境可以通过桥与客户端通信,这个涉及到具体业务,就不细说了,总之,调个API完事。

看起来已经实现了。

开始踩坑。

遇到性能问题

按照需求,后端会控制视频出现的频率,至少5个卡片才允许出一个视频,不然满屏幕视频,效果不好。但这是一个瀑布流,理论上可以滚动加载成百上千个卡片,假设每5个卡片放一个视频,效果会如何呢?

CPU占用情况

大概也就是这样吧,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带来的内存优化算是赠送的吧。

其他问题

判断Wi-Fi导致卡顿

在较为古老的OPPO手机上测试该页面,发现还是有些卡顿,尝试把getNetworkType调用干掉,就继续丝滑了。

原因是每加载一个有视频的卡片时,我都会去判断一下网络类型,以应对用户切换Wi-Fi到4G之类的操作。因为APP的桥没有提供相应的监听函数,只好这样操作。但显然,不是很值得。

于是找到了一个在线和离线事件,可以监听浏览器上线和下线。如果用户从Wi-Fi切换到4G时会经历下线和上线,那真是完美了。当然,没有这么完美,切换网络过程中并没有触发这两个事件……

最终的解决方案是,每10秒调用一次getNetworkType,用轮询折衷一下。

静音播放Bug

静音播放视频似乎没有问题,但是当我点进一个详情页,打开新的webview,再返回到当前页面时,诡异的播出了声音…… 并且,还没有找到原因。

结果不做了

然后发现点评APP首页用的是WebP格式的动图,而不是视频。。。

改方案。。。

改接口。。。

优化基本是白写了,或者说,可以优化,但没必要。。。

(还是学到了点东西的)

最后的结局

平台对WebP支持的稀烂,动图直接没处理,暂时无法支持动图……

所以还是要使用视频。

好消息是这个优化没白做,坏消息是我必须要解决静音变成非静音的bug。

直接提了工单给平台,得知是对方手滑实现的“特性”,目前可以通过监听webview appear事件手动再设置一下muted即可。

这…算是happy ending吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,089评论 4 62
  • 这几天开始践行微习惯,重拾了练字这“行当”。 一开始我是定下了每天练3个字的目标,后来发觉一行字...
    绵泽先生阅读 350评论 2 0
  • 20号早上取车后就开始正式的旅程。前一天还晴空万里的雷市今天一大早就开始下雨,但是坐在车上的我们都还是充满了信心,...
    坤人指路阅读 396评论 2 1
  • 想跟疲惫许久的自己谈谈心。 黑夜漫长,我很喜欢,我想不眠,想看尽世间一切繁华,可惜力不从心,我还没有这个能力,只能...
    村上春树卍阅读 270评论 0 0
  • 今天早上,我没有哼唧,我起床,然后妈妈给我听写了语文。我们把时间调整到早晨读一门经典。很快就读完啦!然后我们一起去...
    易道而行阅读 181评论 0 0