需求定义很简单,原本有一个这样的瀑布流页面,滚动加载更多卡片,在此基础上,增长视频支持,也就是说多是图片也多是短视频。视频要求在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吧。