作者:张婷婷,单位:中国移动智慧家庭运营中心
随着公司的高速发展,业务需求越来越多,用户和公司对于页面的稳定性、性能也有了更高的诉求。根据Aberdeen Group的调研发现从浏览器输入地址开始访问到页面展示的最佳时间为3秒内,每多一秒的延迟会使客户满意度降低16%。页面首屏加载速度就成了重要的前端性能指标,统计首屏时间可以帮助我们更好地分析原因和优化性能,进而提升项目质量、提高用户的使用体验。
Part 01● 白屏和首屏时间 ●
- 白屏:从用户请求页面开始到显示第一个字符的时间。中间包括DNS查询、建立TCP链接、发送首个HTTP请求、返回HTML文档、HTML文档head解析完毕。通常认为浏览器开始渲染<body>标签或者解析完<head>标签的时刻就是页面白屏结束的时间点。
- 首屏:指用户打开网站开始,到浏览器首屏内容渲染完成的时间,对于用户体验来说,首屏时间是用户对一个网站的重要体验因素。
Part 02● PerformanceTiming ●
performance.timing记录了用于分析页面整体性能指标的关键时间点,包含网络、解析等一系列的时间数据。最好在页面完全加载完成之后再使用,因为很多值必须在页面完全加载之后才能得到。最简单的办法是在window.onload(vm.$nextTick 或 react hooks useEffect)事件中读取各种数据。
在浏览器控制台,console输入performance可以查看到performance.timing相关时间节点:
图1 performance.timing参数介绍
通过PerformanceTiming不仅可以帮助我们省去繁琐的手动打点操作,还可以帮助我们获取很多其他数据,对于整个时间节点的对应关系:下图显示了PerformanceTiming中定义的所有时间戳属性。
图2 performance.timing参数说明
比较有用的页面性能数据大概包括如下几个:
重定向耗时:redirectEnd - redirectStart
DNS查询耗时:domainLookupEnd - domainLookupStart
TCP链接耗时:connectEnd - connectStart
HTTP请求耗时:responseEnd - responseStart
解析dom树耗时:domComplete - domInteractive
白屏时间:responseStart - navigationStart
DOM ready时间:domContentLoadedEventEnd - navigationStart
onload时间:loadEventEnd – navigationStart
Part 03● MutationObserver API ●
MutationObserver API让我们能监听dom树变化,在首屏的加载中,会涉及到dom的增加、修改、删除,所以会触发多次MutationObserver。
1)利用MutationObserver监听document对象,每当dom变化时触发回调函数;
2)判断监听的dom是否在首屏内,如果在首屏内,将该dom放到指定的数组中,记录下当前dom变化的时间点;
3)在MutationObserver的callback函数中,通过防抖函数,监听document.readyState状态的变化;
4)当document.readyState === 'complete',停止定时器和 取消对document的监听;
5)遍历存放dom的数组,找出最后变化节点的时间,就是首屏加载完成的时间。
监听container外层容器的变化,当触发回调函数时,判断对应的事件类型以及新增加的子dom是否是首屏展示的dom节点。
尽管现在的MutationObserver在移动端兼容性比较好,但为了更好的兼容,我们可以另外引入MutationEvents API。
Part 04● webview里H5页面首屏时间 ●
4.1 WebView初始化阶段
该阶段包括几个主要步骤:
(1)开始解析Url(Url中可能包含${}需要解析的字段)
(2)完成解析Url
(3)开始校验Url是否可以打开
(4)结束校验Url
(5)开始加载Url到webview容器
和家亲app jsbridge提供了getPerformInfo方法可以帮助我们获取WebView性能数据:
WebViewUrlLoaded WebView加载url时间:startLoadUrl(开始加载Url到webview容器) - startProcessUrl(开始解析Url);
4.2 HTTP请求服务阶段
该阶段包括几个主要步骤:
(1) DNS查询
(2) 等待 TCP 队列
(3) TCP链接
(4) 发起http请求和响应
(5) 服务器端处理HTTP请求,服务器端处理HTTP请求,浏览器得到html代码
(6) 开始head解析
该阶段的时间从webview容器开始加载Url开始到完成head解析,可以使用window.performance.timing.responseStart - startLoadUrl(开始加载Url到webview容器)
4.3 静态资源下载
该阶段包括几个主要步骤:
(1) head解析并开始请求静态资源(如js、css、图片等)
(2) 静态资源下载完成
(3) 开始解析静态资源
该阶段的时间从开始请求静态资源,到开始解析静态资源(比如JS),我们可以在js文件开始自定义JscriptLaunch字段,并赋值给window.preformance对象,(window.PerformInfo || (window.PerformInfo = {})).JscriptLaunch = new Date().getTime(); 以便于我们统计该阶段的时间:window.PerformInfo.JscriptLaunch - window.performance.timing.responseStart。
4.4 API调用和首屏dom渲染
该阶段包括几个主要步骤:
(1) 开始解析静态资源,请求API
(2) 响应数据
(3) 下载资源并渲染dom
(4) 首屏内容加载完成
利用MutationObserver监控DOM的变化,获取首屏dom加载完成的时间,用该时间点减去window.PerformInfo.JscriptLaunch,就获得了该阶段的时间。
Part 05● 数据上报 ●
将统计到的数据以图片打点、fetch请求或Beacon等形式进行上报,可以帮助我们后续进行分析和优化。
5.1 直接发请求上报
直接将数据通过ajax发送到后端有一个问题,就是在页面卸载或刷新时进行上报的话,请求可能会在浏览器关闭或重新加载前还未发送至服务端就被浏览器cancel掉,导致数据上报失败。
5.2 利用图片上报
服务器端并不关心具体的数据上报方式,无论是请求image文件还是请求其他普通文件(JS)或者是请求接口,可以进行数据上报。
使用image有几个优点:
图片的src属性并不会跨域,不会出现跨域问题;
大部分浏览器会延迟卸载(unload)文档以加载图像,可以避免第一种方法的问题;
只要在js中new出Image对象就能发起请求,不用插入dom中,可以防止阻塞页面加载,影响用户体验;
相比PNG/JPG,GIF的体积最小,最适合进行上报,一般采用1*1像素的透明gif进行上报。
5.3 css定义content
通过css定义content,按钮点击就会上报,但是不能动态传入一些变量。
5.4 Beacon
Beacon可将数据异步发送至服务端,且可能保障在页面卸载实现前发送申请(解决页面卸载会终止请求的问题)。
Part 06● 总结 ●
页面首屏加载时间是个重要的性能指标,通过上面的方式对首屏时间进行统计分析,可以帮助我们有针对性的进行性能优化。同时页面性能的提升,带给用户更好的产品体验,这样才会得到好的产品反馈,给企业带来价值。