在统计页面性能监控时,首屏渲染的时长尤其重要,对于用户是否可以很直观的看到页面效果,留住用户时一个关键的指标,但是我们要如何统计呢?
基本统计方法:基于w3c性能小组api
var perfData = window.performance.timing;
var renderTime = perfData.domComplete - perfData.domLoading;
但是前端框架的出现,spa的盛行,这种简单的方式已经不满足现状。真的这种情况,google提出了FMP(first meaning paint)概念,即主要内容可见的时间。本文就对如何计算主要内容时间做一下分析
基本计算方法:计算布局对象
从图片中我们可以看出,当布局对象的增加,页面也是一点点被加载出来。页面布局对象的变化与页面渲染进度相关,当页面布局发生显著变化的时间,与页面主体渲染的时间非常接近。于是第一次尝试的基本算法就近似于:页面的首次有效绘制 = 页面最大布局变化的绘制
长页面
区别于上面的普通页面,对于长页面来说,页面首先加载的是dom树结构,其余文件是采用懒加载的方式呈现出来的。这有别于上面的方法,因为页面在首次渲染的时候,dom树就已经发生了剧烈变化,但是首屏的主要内容是在懒加载之后呈现出来的。布局显著性,页面越长,显著性系数越小。布局显著性 = 添加的对象数目 / max(1, 页面高度 / 屏幕高度)。区别于懒加载还有一种情况和这个类似,就是如果加载出来的dom节点是不可见的,那么即使加载出来再多的节点,对于用户来说都是无用的。
新的统计方法
所以基于以上的原因,我们重新整理一下计算方法:
- 1、首先监听页面元素的变化,监听每个dom,然后通过performance.now - performance.timing.fetchstart进行打点计算。
- 2、遍历每次新增的元素,并计算这些元素的得分综合。
- 3、计算元素权重,如果元素可见计1分,不可见计0分。
- 4、排除无用的元素标签如script、head、meta等。
- 5、计算元素的显示位置,如果超出屏幕计为0分,否则计为1分。
- 6、通过元素的getBoundingClientRect方法获取当前的高度,然后与可视窗口的高度进行对比,即可得知当前元素是否是首屏内的内容节点。
- 7、dom渲染统计完成不等于首屏时间计算完成,页面还没有显示完整,还包含图片的加载,与显示。此时我们需要根据浏览器提供的performance API来统计图片加载完成时间。
- 思路:通过getEntriesByType('resource')来获取initiatorType为img的元素,判断资源的请求时间fetchStart与dom渲染完成时间对比,如果请求时间比dom渲染完成时间早的,则认为其是首屏渲染的一部分,所以对比渲染时间和图片的返回时间responseEnd,比较大的就是渲染时间,然后以此逻辑便利所有图片,更新首屏渲染时间。
基于以上想法的优化:如果每次都去遍历新增的元素是非常消耗性能的,实际上采用深度优先算法,如果子元素可见,那父元素可见,不再计算。通过以上的方法,我们可以得到最终的fmp的值,就是整个可视区域的dom变化。
总结
ssr渲染的页面,首屏时间就是dom渲染结束的时间,spa的项目使用的就是fmp的时间。最终细致化的思路就是监听dom,分析dom的位置信息和可视窗口最对比,然后再通过performance.getEntriesByType('resource')去解析img资源的fetchstart时间和上面计算出来的时间做对比,如果比计算出来的时间早,说明该图片是首屏内的资源,然后在通过图片的responseEnd时间与上面的时间对比,数值比较大的即是首屏渲染时间
后续补充
- 一、getBoundingClientRect用于获取元素相对于视窗的位置 理论上我们只要计算每个元素的位置信息,结合视窗的高度信息,就可以判断元素是否处于屏内。但是实际情况下,一个页面的dom数量是非常大的,大量的dom操作会影响页面性能,getBoundingCientRect会引起页面重排。此方案不理想
- 二、intersectionObserver + MutationObserver IntersectionObserver通过启动一个观察器,以一种异步的方式检查目标元素是否出现于视窗中,它返回的数据里面包含了两个重要信息:
- time:元素可见性发生变化的时间
- intersectionRatio: 目标元素的可见比例,介于0.0 - 1.0,为0时表示元素不可见,为1时表示元素完全可见