当前位置: 首页 > 科技观察

实践中的前端性能优化

时间:2023-03-20 17:18:21 科技观察

你无法管理你无法衡量的东西。-彼得·德鲁克。衡量名言参考了彼得德鲁克的名言,“如果你不能衡量它,你就不能管理它”,绩效也是如此。如果没有准确的解决方案来衡量性能,优化是不可能的。那么对于我们来说,有哪些指标可以用来衡量页面性能和用户体验呢?我将从以下几个角度一一为大家讲解:PerformanceAPI页面流畅度FPSraf(requestAnimationFrame)首屏性能FP,FCP、FMPCWV(CoreWebVitals)PerformanceAPI相信前端同学应该不陌生应用程序接口。通常,我们将浏览器提供的可用于测量和采集的API统称为PerformanceAPI。这种类型的对象可以通过调用只读属性来访问。window.performance来搞定。在日常工作中,为了计算一个任务的耗时,我们通常会使用Date.now()在任务执行前后创建两次,最后取两者之差作为任务执行的耗时。但是Date.now()有两个问题:返回的时间戳是自1970年1月1日00:00:00UTC以来的毫秒数,这取决于系统时间。精度仅低至毫秒(ms)。为了准确计算性能,我们可以选择PerformanceAPI提供的performance.now()进行高精度计算。它的特点是:时间戳是根据页面被打开的时间来计算的。精度精确到微秒(us)。下图可以更直观的看出两者的区别:页面流畅度FPS帧率FPS(FramesPerSecond——每秒传输的帧数),一般对于网页来说,最佳帧率是60FPS,如果higher如果该值接近这个值,页面会更流畅。如果帧率远低于这个值,用户可能会明显感到卡顿。60FPS意味着页面需要每16.5ms(1/60)渲染一次,否则会出现丢帧,浏览器中的JavaScript执行和页面渲染会互相阻塞。如果有非常复杂的逻辑占用了大量的执行时间,会导致页面卡顿。在Chrome的devtools中,我们可以执行Cmd+Shift+P,输入showfps快速打开fps面板,如下图所示:通过观察FPS面板,我们可以很方便的监控当前页面的流畅度。如果我们想在代码中监控当前页面的FPS帧率,可以参考下面的示例代码:varlastTime=performance.now();varframe=0;varlastFameTime=performance.now();varloop=function(time){varnow=performance.now();varfs=(now-lastFameTime);最后成名时间=现在;varfps=Math.round(1000/fs);框架++;if(now>1000+lastTime){varfps=Math.round((frame*1000)/(now-lastTime));框架=0;最后一次=现在;};window.requestAnimationFrame(loop);}requestAnimationFramewindow.requestAnimationFrame()告诉浏览器你要执行一个Animation,并要求浏览器在下次重绘前调用指定的回调函数更新动画。这里借用MDN的描述,顾名思义,就是传入一个函数,让浏览器在下次渲染之前调用它。基于这个特点,结合上面提供的FPS计算示例代码,我们可以发现,如果继续调用requestAnimationFrame,每次调用的间隔应该在16.7ms左右,满足我们60FPS的页面流畅度要求。您可以使用以下代码在控制台中尝试一下:letlastTime=0;constmeasure=()=>{console.log(`${Date.now()-lastTime}ms`);lastTime=Date.now();requestAnimationFrame(measure);};measure();首屏性能首屏性能作为我们最关心的核心指标之一,在性能优化场景中占据了相当大的比重,那么对于首屏性能我们有哪些衡量指标呢?针对这一问题,谷歌提出了一系列以用户体验为中心的性能指标。FP、FCP、FMPFP(FirstPaint译为“第一次绘制”)表示浏览器第一次将像素传输到屏幕的时间,只表示绘图已经开始,实际意义比较小。FCP(FirstContentfulPaint译为“首次内容绘制”)表示浏览器首次向屏幕绘制“内容”(仅首次绘制文字、图片(包括背景图片),非白色)。相反,FCP是指浏览器第一次从DOM绘制内容。例如:文本、图片、SVG、画布元素等。这个时间点称为FCP。FMP(FirstMeaningfulPaint译为“第一次有效绘画”)表示页面中有意义的内容开始出现在屏幕上的时间点。也是我们衡量用户加载体验的主要指标。FMP本质上是一种主观认知指标。它是用一种算法来猜测某个时间点可能是FMP,但是计算方法过于复杂,不够准确。后来Google也放弃了FMP检测算法,采用了更清晰客观的Indicator——LCP。CWV(CoreWebVitals)核心网络指标是适用于所有网页的网络指标的一个子集,每个网站所有者都应该衡量,并且还会显示在所有Google工具中。每个核心Web指标代表用户体验的不同方面,可以实际衡量,并反映以用户为中心的关键结果的真实体验。当前的Web核心指标包括页面加载性能、交互性和视觉稳定性三个方面,包括以下三个指标和阈值:LargestContentfulPaint(LCP):LargestContentfulPaint,衡量加载性能。为了提供良好的用户体验,LCP应在页面首次开始加载后的2.5秒内发生。首次输入延迟(FID):首次输入延迟,一种交互性度量。为了提供良好的用户体验,页面的FID应为100毫秒或更短。CumulativeLayoutShift(CLS):累积布局偏移,衡量视觉稳定性。为了提供良好的用户体验,页面的CLS应保持在0.1。或更少。1)LCPLCP关注首屏最大元素的渲染时间。与FCP不同的是,FCP更关注浏览器什么时候开始绘制内容,比如加载页面或者骨架屏,这些内容没有实际价值,所以LCP因为FCP更适合作为首屏指标。以详情页为例。在FCP中,产品图像尚未加载。这时候对于用户来说,一个几乎白屏的页面是没有交互价值的。在LCP中,图片已经加载完毕,首屏的主要元素也加载的差不多了,将此时的时间作为首屏时间,更贴近用户的体感。既然LCP是根据页面上面积最大的元素的渲染时间来确定的,那到底包含了哪些元素呢?图片嵌入到svg中的image元素中。视频的封面是通过url()加载的。背景图文字在网页测试上可以很直观的看到当前LCP元素的详细信息:元素面积的计算规则如下:视口中可见元素的大小,如果超出可见区域或者被裁剪或者blocked,它不会被包含在元素的大小中。对于一个图片元素,尺寸是图片实际尺寸与原始尺寸中较小的值,即Min(actualsize,originalsize)。对于文本元素,只取能覆盖文本的最小矩形区域。对于所有元素,margin、padding、border等都不算在内。2)FIDFID指标是指从用户第一次与网站交互到浏览器响应事件的实际延迟时间。你可以想象一下,如果你点击一个按钮后页面没有任何变化,2-3秒后就会开始响应。可想而知,这次体验非常糟糕。FID决定的交互行为包括:点击、触摸、按键等(不包括滚动和缩放)。有事件绑定行为,例如在dom上注册的单击事件。那么为什么会出现交互延迟呢?例如,我在按钮上注册了一个点击事件,例如:btn.addEventListener('click',()=>{//dosomething})正如预期的那样,当用户点击按钮时,回调函数将是直接触发,但是如果当前主线程被渲染或者LongTasks占用,这个回调的执行会延迟,会增加FID时长。但是,FID作为一个“非客观值”,需要通过用户交互进行采集,而用户交互的时机也会影响指标的采集和统计。3)CLSCLS是用来衡量视觉界面稳定性的指标,指的是页面产生的连续累积布局偏移分数。在日常业务中,我们经常会使用懒加载、骨架屏等方式,先以较低的成本展示页面框架,然后再使用动态渲染来填充页面内容。如果此时布局发生变化,比如动态加载的元素大小与原元素大小不一致,可能会导致用户误操作,影响用户体验。CLS的存在就是为了衡量这些问题。当我们说布局偏移时,是指页面中可见元素的起始位置发生变化,元素的添加或删除不会触发布局偏移。那么如何定义偏移量的连续累积呢?有以下要素:CLS计算的不是页面整个周期的偏移量总和,而是累计值最高的连续布局偏移量。相隔小于1s且整个窗口的最大持续时间为5s的偏移被计为连续偏移。分析工具DevToolsDevTools是对付前端同学使用最多的工具之一。主要用来查看日志,查看网络请求,调试页面等,我们也可以用它来分析页面性能。网络如上图所示。这就是我们非常熟悉的网络接口。功能方面,我用红框大致划分为:选项区:PreverseLog可以在面板中保留网络请求,当页面跳转或者页面跳转时可以保留上一页的日志;DisableCache可以屏蔽浏览器的http缓存机制;右边的Nothrotting选择器可以模拟当前网络状态(Fast3G、Slow3G等)。请求列表,请求状态。请求传输大小:默认显示gzip/br压缩后的大小。Waterfall:资源加载的时机和每一步的耗时。Performance性能面板可以提供更专业的性能信息。WebPageTestWebPageTest是一个在线性能分析平台。除了常用的cwv性能数据外,它还有性能、灯塔报告、页面对比等功能。输入网址后,我们可以简单的选择一个简单的配置进行测试。默认情况下,将执行3个测试。在这里我们可以看到页面的一些核心指标,我们可以点击相应的指标项进行更详细的分析。在Waterfall页面,我们还可以直观的看到请求顺序、请求时间、关键节点(F??CP、LCP等)。优化方法网络传输优化这里重点关注三个时间指标:TotalConnectionTime:整体连接时间TTFB(TimetoFirstByte):第一个字节传输时间ContentDownload:内容传输耗时TotalConnectionTime导致连接耗时有可能时间长短的因素很多:机器和用户终端之间的物理距离太远(美国-中国)。重复建立连接,页面中使用了多个不同的域名,每次都需要重新建立连接。用户端网络环境问题。那么我们可以采取什么措施来解决这些问题:利用CDN动态加速主域名,缓存资源域名,利用边缘节点的特性来缩短用户请求的距离。使用pre-connect来预建域名,同时收集域名,这样在http2的情况下可以减少建连接的耗时。充分利用http缓存和servicesworker请求拦截的特性,将可缓存的资源缓存在本地,减少发起的网络请求次数。TTFB+ContentDownloadTTFB是从发起请求到收到服务器请求的第一个字节的时间。一般来说,如果首屏html请求的TTFB能达到100ms以内,就已经是不错的体验了。如果超过500ms,那么用户可以明显感觉到白屏。准确的说,TTFB是完成DNS查询、TCP握手、SSL握手后发送HTTP请求报文到收到服务器第一个响应报文的时间差,约等于ARTT(Round-TripTimeis往返延迟)+ServerRT。那么当TTFB耗时较长时,如何优化呢?可以参考以下方法:减少请求传输量,避免无用信息。减少服务器端处理时间(增加缓存、缓慢的SQL管理等)。首屏HTML内容的流式渲染,因为浏览器对HTML的解析并不依赖于下载完整的HTML,而是解析并渲染一部分,所以服务端可以先将准备好的部分内容通过流式渲染返回,而不用等待一切返回前做好准备。懒加载:先返回必要的内容,比如超长页面,可以先返回首屏看到的内容,其余通过异步加载渲染,通过多个接口请求。那么,TTFB是不是越短越好呢?其实也不一定。我们需要在TTFB和内容下载之间取得良好的平衡。比如我们开启gzip/br压缩后,TTFB必然会呈现上升趋势,但对应的资源量会发生变化。小的话会加快传输时间,减少ContentDownload时间,所以我们应该关注真实的用户体验,而不是盲目盯着时间去优化。preloadpreload也是预加载。预加载的方式有很多种,内部终端和外部终端也有不同的解决方案。比较常见的有:preload标签:serviceworker预加载:flasher、workbox-preload等zcache:通过资源离线包在客户端预加载。相关链接Lighthouse评分计算器:https://googlechrome.github.io/lighthouse/scorecalc/WebPageTest:https://www.webpagetest.org/WebVitals:https://web.dev/vitals/web-vitals-Github:https://github.com/GoogleChrome/web-vitals