
前端开发工程师
曾经在拼多多,美团,七牛云等公司搞过客服系统、数据中台、实时音视频等项目,在前端开发领域有些体会。
前端开发工程师
曾经在拼多多,京东等公司做过客服系统、C端商城等项目,在前端搭建有些许心得。

导入篇
本文涉及到一些专业的词汇,需要先解释一下。SPA(又称 CSR) 和 SSR 是两种渲染模式,分别表示浏览器端渲染和服务器端同构渲染。他们的区别是首页的渲染时机不同。视口(viewport)表示用户看到的区域。在典型 h5 场景中,页面至少有一屏,当用户在屏幕上上下滑动时,页面中的部分内容会进入视口,部分内容会离开视口。有内容的元素相反的概念是空元素,表示任何用户能够看到的元素。秒开率计算包含的时间有两部分,一个是 native 打开 webview,另一个是 webview 加载 HTML 文档并渲染。具体解释如下:
SPA: 浏览器端渲染,内容由浏览器执行 js 渲染出来,又称 CSR
SSR:服务端渲染,内容在服务端生成,返回的 HTML 即是渲染后的页面
Lighthouse:谷歌推出的分析网页性能的工具
视口:设备屏幕上用来显示网页的那部分区域
有内容的元素:任何文本、图像、视频、SVG 或者非空白的 Canvas
秒开率:从用户点击到页面渲染出来的时间在 1 秒内的比例
性能指标
用户体感指标
常见的用户体感指标有 FCP 、LCP 和 FMP 等。他们的含义如下:
FCP(First Contentful Paint): 首次出现有内容元素的时间
LCP(Largest Contentful Paint): 视口内出现最大有内容元素的时间
FMP(First Meaningful Paint): 首次绘制“主要内容”的时间
FCP 最早出现,表示首次出现有内容元素的时间。FCP 并不能反映页面完全渲染出来的时间,和真实体感相比会偏小。下图是一个典型的例子,该页面做了加载动画,FCP 表示的是加载动画的时间。后来 Google 推出了 FMP,字面上的意思是首个有意义的内容渲染时间。Google Lighthouse 中 FMP 表示首屏最大布局变化的时间。后来这个指标由于过于敏感、不稳定又被 LCP 取代。
LCP 指的是视口中最大有内容元素的渲染时间。LCP 值一般都会比 FCP 大,更能代表页面渲染的情况。但是最大有内容元素和用户真实体感之间还是有差距。如下图所示,该页面的 LCP 表示的是页面布局渲染出来的时间点,这个时候页面中的商品还没渲染出来,页面也不稳定,抽奖组件也还在渲染,还没到达最终的显示效果。用户不能点击,也没有足够的信息支撑下单、继续浏览等操作。所以我们还需要寻找更能反映用户体感的指标。
最后我们借用了 FMP 的名称,自行计算了一个新的指标。它表示的是视口中重要元素渲染完成后的时间。这里的重要元素就是我们根据一般用户关心的内容动态计算出来的。如下图所示,该页面的 FMP 几乎代表了页面完全渲染出来的时间,这个时候有足够的信息引导用户点击。

指标 FMP 的计算
FMP 计算的关键是找到一种定量衡量用户体感的方式,让“重要元素”真正代表用户关心的内容。最后我们使用权重来筛选出“重要元素”。一般情况下图片、视频比起文字更能吸引用户,其中视频、动画更能吸引眼球。所以视频元素的权重 > 图片元素的权重 > 文字元素的权重。从另一个维度来看,一般元素面积越大越能吸引人的注意力。所以权重的计算可以表示为:
权重 = 元素在视口中的面积 * 该元素的单位权重
单位权重是我们自己设定的参数,目的是为了让“重要元素”和用户关心的内容相匹配。
我们的 h5 页面中有大量的两列、三列商品流。在商品流组件中用户应该是需要看到图片才能会决定是否点击,所以我们必须满足这种场景下,图片是“重要元素”。而太小的图片也不适合作为“重要元素”,一来太小的图片用户本来就不太关心;其次选取太多“重要元素”会使得指标过大,也会造成和用户体感不符。所以我们的单位权重参数设定的参照是在各种移动端屏幕下,三列商品流中的图片能够恰好成为“重要元素”。下面两个图中,红色线框中的是选取出来的“重要元素”。大致包含了用户比较关心的信息。

FMP 的计算分为三步:
选出对用户而言的“重要元素”
计算重要元素的渲染时间
选取重要元素中最大的渲染时间即为 FMP
筛选“重要元素”的原则上文已经介绍过了。计算渲染时间时,需要考虑文字渲染和多媒体资源渲染两种情况。文字渲染直接使用元素变更时间(后文细讲),多媒体资源渲染时间取资源加载和元素变更时间的较大值。因为多媒体资源元素变更和资源加载的顺序是不确定的。
具体的计算方式是利用 MutationObserver 对象监听 body 元素及其子元素的变动,每次添加元素时给新的元素标记上变更次数,同时利用 performance.mark 记录变动的时间。当页面稳定后,计算所有元素的权重值,筛选出权重大于 body 的元素为“重要元素”。计算出这些“重要元素”的渲染时间,元素变动时间通过 performance.getEntriesByType('mark') 获取之前标记的数据。资源加载时间通过 performance.getEntriesByType('resource') 获取。
选取 body 为是否是“重要元素”参考坐标的好处是当没有符合条件的“重要元素”时,可以选择 body 为“重要元素”作为兜底。计算时间时应该尽量统一计算方式,尽量都是用 Performance Api 来计算。这样误差会较少,遇到 performance.mark 兼容性问题时,也尽量使用 performance.now,再不行才考虑采用 Date.now 。下图为新人专区页面计算 FMP 过程中打印出来的日志。第一张图片中 FMP 值 2592 ms 就是这样计算出来的。

文档渲染流程
渲染流程可以简化为加载 HTML 文档,解析 HTML 文档,加载文档中资源这三个阶段。各个阶段做的事情大致如下图所示。每个阶段都会触发一些事件,其中加载 HTML 文档过程中触发 unload 事件,解析 HTML 文档结束后触发 DCL(domContentLoaded)事件,加载文档中资源结束后触发 L(onLoad)事件。

这里需要关注的是在解析 HTML 文档时,浏览器会请求 API 接口,创建动态元素加载静态资源。这两个操作是异步的,没有被纳入文档渲染流程里面。这就导致了 DCL、L 并不能反映用户体感。
用户体感指标和文档渲染流程之间的关系
传统静态页面其实能用 DCL 和 L 反映用户体感。因为当页面解析完时,dom 也构建结束了,这时 FCP,LCP 甚至 FMP 都结束了。SSR 由于在服务端渲染,返回的 HTML 文档中已经包含了渲染后的首页信息。时间线和传统静态页面差不多。大多数情况下,各个时间点顺序如下:

SPA 模式下,由于需要浏览器执行 js 进行渲染,DCL 一般会在渲染之前。大多数情况下的时间线如下图:

一般情况下 FCP、LCP、FMP 三者的顺序相对固定, FCP <= LCP <= FMP。FCP 和 DCL 的先后顺序看服务端返回的 HTML 文档中是否含有有内容的元素,如果含有有内容元素,则在解析的过程中,页面就开始渲染,FCP 早于 DCL;反之 FCP 晚于 DCL。LCP、FMP 跟 DCL 的关系就要看具体的页面类型,像纯文本简单页面 FCP = LCP = FMP 时,LCP、FMP 也有可能在 DCL 之前,但是大多数时候都在 DCL 之后。三个体感指标和 L 的先后顺序要看请求 API 接口和动态创建元素的情况,假设 API 接口调用很耗时,而元素都是在 API 接口数据返回后动态创建的,那么也有可能 FCP、LCP、FMP 都晚于 L。
性能分析
借助于前端性能监控,我们能够比较方便地对整个业务领、具体页面的性能进行分析。鉴于我们同一个业务领往往一个 repo,而且有同一波人开发维护的情况,我们在性能监控中添加了业务领维度。在大盘上直接看到的是业务域的整体性能。性能分析可以从业务域性能分析和页面性能分析两个个方面入手:
业务域性能分析,主要的功能是找出最该优化的页面、排查异常指标
页面性能分析,主要的功能是找出页面中待优化的静态资源和 API 接口
下面就分别介绍相应的功能
找出最该优化的页面
这个比较简单,在业务域分析页面中的页面分析模块中选择 pv 前 10 的页面,按照 pv 倒序排序,看性能指标比较差的页面即可。pv 越高的页面性能优化后对整体指标的贡献越大,而性能指标较差的页面优化潜力越大。下图中可以考虑优化第一个页面。

找出页面中待优化的静态资源和 API 接口
分析 API 接口和分析静态资源的手段一样。通过资源列表和时序图来分析。下面就以静态资源为例进行介绍。在资源加载分析模块中切换到静态资源 tab 查看。按照传输体积、传输耗时、CDN 覆盖率这些字段依次倒序排序,利用预览的功能,找出画面简单的图片、没有压缩过的 JS/CSS 资源等。

时序图功能和 chrome 浏览器中 dev tools 中网络分析的功能类似,区别是这里的时序图体现的是所有用户整体的性能数据,覆盖了各种机器型号、网络情况和各种极端情况。利用时序图可以查看 FMP 前是否只加载了必要的文件,是否利用了浏览器并行加载资源能力,资源加载等待的情况是否可以减少。

排查指标异常
我们在使用前端性能监控时,多多少少会遇到一些不合常理的情况,这时就需要对异常情况进行分析。
常用的分析步骤
1、分析列表,主要看pv比较大的页面的性能指标,哪些性能较好,哪些性能较差;
2、分析曲线图,看看页面流量变化的趋势,性能变化的趋势;
3、看两者是否有关联性,如果有关联性,则考虑两者之间是否为因果关系,如果为因果关系,再把他们放到一张曲线图中观察是否呈正相关、或者负相关关系。
具体的情况可能有很多,超出本次分享范畴了,这里不再展开。
常见的异常情况
下面介绍两种经常遇到的异常情况,看看和你的认知是否有出入:
1、Android 和 iOS 数据波动不一致,比如 Android 秒开率提高,iOS 秒开率变低。原因:
不同平台用户类型不太一样。(从下图中可以看出,android平台新用户比较多,ios平台没有那么多)
不同平台页面流量变化趋势也不一样。

2、业务整体秒开率在没有发版的情况下波动。原因:
每个页面的性能数据不一样。
每个页面的流量比例会发生变动。

优化手段及效果
搭建会场下的页面性能优化
得物APP内h5的项目都是通过webview打开。对于webview的性能大家普遍的印象就是打开速度比native慢。
一个普通的用户打开webveiw访问h5需要经过如下过程:
点击APP入口(例如banner等)
到达新页面,页面白屏
页面基本框架出现(骨架屏),但是没有数据,页面处于loading状态
出现所有数据,页面完全呈现
从程序执行的角度,我们来看一个没有优化过的webview加载h5的过程:

压缩每一个阶段的时间,是性能优化的关键点。
WEBVIEW
静态资源内置

html预加载
app冷启动回主动拉取关键入口的html做缓存

H5优化
SPA与SSR
SPA页面渲染整个流程:

SSR页面渲染整个流程:

SPA与SSR在FMP上的表现:


加载时序优化
webp rem 以及bridge的js block了html的加载


减少三个block的script的加载,粗略估计节约 >>120ms的fcp
资源体积
图片优化
1、webp
webp能够达到90%压缩率,其重要性不言而喻。
现有方案在ssr直出的组件没有webp的能力。即使端上支持webp也加载了jpg或者png的图片,导致资源太大。而在ios的14版本后ios有了支持webp的能力,咨询了IOS同学,14版本的占比至少百分之七八十。
2、方案
node端直出所有图片都为webp。在端上做一次webp的check,不支持则降级到原jpg或者png。
3、效果
不支持webp的手机:注意头图

支持webp的手机:

无损压缩服务
从图片源头上解决图片过大的问题,使用了开源方案。
会场传图统一收口交互,避免同一张图多次上传的情况。平均压缩率60%

包体积
无用自定义组件的删除 -33k
按需lodash的大小 -24K
神策SDK 通过JS创建script加载。且解决神策sdk的命名空间前置访问 -76 k
koa-router的依赖 -21 k
组件按需加载

总资源优化数据

上线后第一天优化数据

Lighthouse 打分维度分析结论
Lighthouse 综合打分
优化前

第一期优化后





业界对比(场景不一样,分数仅供参考,没有绝对对比性,越复杂性能越难优化):
天猫 20分
京东 12分
拼多多 32分
结论:Lighthouse 相应提升了 20%+
浏览器资源维度数据分析结论
优化前数据:

第一期优化后:

结论:transferred 提升了 20%
综合分析结论
第一期优化根据各种数据汇总,性能整体提升 10%
SSR组件体验专项优化
现状
有些组件在node端没有直出,且没有图片懒加载的能力。
方案
node端直出组件,且屏外的图片使用懒加载的功能。
效果
前:涉及以一键领券。分会场入口

后:

FMP总效果

1
END
1

【得物技术】
扫一扫关注公众号




