作者:京东科技 郝梁
前言:作为 C 端前端研发,除了攻克业务难点以外,也要有更深层的自我目标,那就是性能优化。这事儿说大不大,说小也不小,但难度绝对不一般,所涉及的范围优化点深入工程每个细胞。做好前端性能优化绝非简单之事!文章主要内容介绍前端性能考核指标及优化方案。
根据 chrome Lighthouse 最新规则,前端性能指标考量主要有 FCP(First Contenful Paint)、SI(Speed Index)、LCP(Largest Contentful Paint)、TBT(Total Blocking Time)、CLS(Cumulative Layout Shift) ,占比分别如下。
FCP: First Contentful Paint 首次内容绘制是指测量页面从开始加载到页面内容(文本、图片、背景图、svg 元素或非白色 canvas 元素)的任何部分在屏幕上完成渲染的时间,是测量加载速度感知的重要指标之一。
示例:
从上图可以观察到,页面加载开始到页面渲染完成的时间轴中,FCP 发生在第二帧,首批文本和图片在屏幕上已经渲染完成。
虽然页面一部分内容已完成渲染,但这并非页面所有内容全部完成渲染;这就是首次内容绘制(FCP)与最大内容绘制(LCP)最重要的区别。
FCP 性能值:首次内容绘制完成渲染时间应控制在 1.8s 以内。
我们可以从以下方向点优化 FCP :
SI:Speed Index 衡量页面加载期间内容以视觉方式显示的速度。 Lighthouse 首先捕获浏览器中页面加载的视频,并计算帧之间的视觉速度。通俗的讲,就是网页从有东西到完全显示内容的可见填充速度。
Speed Index 指标值:
我们可以从以下方向点优化 Speed Index 的方法:
LCP: Largest Contentful Paint 最大(最有意义)内容绘制,是指根据页面首次开始加载的时间点来报告可视区域内可见的最大图像或者文本块完成渲染的相对时间。
LCP 指标值:
主要考量以下几种相关元素:
最大内容绘制(LCP)的元素大小是指用户在可视区域内可见的大小,所以考量都是基于可视区域为准,如果元素有延伸到可视区域外,或者元素被裁剪或包含不可见的溢出,这些部分不计入元素大小;
对于图像元素的大小,指标会对比可见尺寸与原始尺寸,取尺寸小者为准;例如双倍图以可见尺寸为准,拉伸放大图则以原始尺寸为准;
对于文本元素,元素的大小为文本节点的大小(包含所有文本节点的最小矩形);
WARNING: 所有元素通过 CSS 设置的任何边距、填充或边框都不在考量范围内。另外如果设置了满屏背景图,但屏幕可视区域内有占比较大的元素(浮在背景图上的元素),导致背景图暴漏可视范围较小,那么最大内容会选择可视区域内最大元素。
并且,一个元素只有在渲染完成后对用户可见后才能视为最大内容元素。
因为网络或技术原因,网页的加载通常是分段进行的, 所以最大元素也在发生变化。
为了应对这种变化,浏览器在绘制第一帧后立即分发一个 largest-contentful-paint 类型的 PerformanceEntry (代表了 performance 时间列表中单个 metric 数据;performance.getEntries() 获取时间列表数据),用于识别最大内容元素。渲染后续帧之后,浏览器会在最大内容元素发生变化时分发另一个 PerformanceEntry 。
页面的元素(某一个元素)只有在渲染完成后并且对用户可见后才能视为最大内容元素。未加载的图像不会视为渲染完成,也就不能视为最大内容元素。字体阻塞期使用字体的文本也是如此。这些情况下,较小的元素会被报告为最大的元素,但一旦更大的元素渲染完成,则会上报另一个 PerformanceEntry 对象。
除了延迟加载的图像与字体外,页面可能会在新内容(接口请求等)可用时向 DOM 添加新元素内容。如果有一个新元素大于先前的最大内容元素,则浏览器还会上报一个新的 PerformanceEntry 对象。
如果当前的最大内容元素从可视区域被移除(甚至从 DOM 中被移除),那么除非有一个更大的元素完成渲染,否则该元素将持续作为最大内容元素,不会更改 performanceEntry 对象。
当用户与页面进行交互(通过轻触、滚动或按键)时,浏览器将立即停止上报 PerformanceEntry 对象,因为用户交互通常会改变页面原有内容。
浏览器出于安全考虑,对于缺少 Timing-Allow-Origin 标头的跨域对象来说,是无法得到图像渲染时间戳的,只有图像加载时间戳。正确的设置 Timing-Allow-Origin 标头,可以获取更准确的指标值。
情况1:对元素大小或位置修改不会生成新的 LCP 候选对象,只有元素在可视区域内的初始大小和位置会被纳入考量范围;
情况2:最初在可视范围内渲染,然后被移除可视区域外的元素仍将报告他在可视区域内的初始大小;
情况3:而在屏幕可视范围外渲染完成,过度到屏幕上的元素则不做报告。
示例:最大元素随着内容加载完成而发生改变
第一个示例中,新内容渲染完成,因此使最大元素发生了改变。
第二个示例中,由于布局的改变,先前的最大内容从可视区域中被移除了。
如果延迟内容没有初始最大元素大,则 LCP 取初始值。
页面上最重要元素并非最大元素,这个时候开发者考核指标是最重要元素。
影响页面渲染性能主要原因有以下几点,通过优化它们可以降低 LCP 指标值
是指浏览器从服务器接收内容所需的时间越长,用户在屏幕上所渲染内容的时间就越长。更快的服务器将直接影响包括 LCP 各项指标的加载值。
可优化方向:
浏览器渲染内容之前需要先解析 DOM 树,解析过程中,如果遇到任何外部样式表(<link rel="stylesheet">)或同步 JavaScript 标签(<script src="main.js">),则会暂停解析。
所以脚本跟样式都是阻塞渲染的资源,这些资源都会导致 FCP 延迟,从而导致 LCP 延迟。所以延迟加载非必要的 JS 和 CSS ,从而提高网页主要内容加载速度。
减少 CSS 阻塞时间的方法:
减少阻塞渲染的 JavaScript 数量能够让渲染速度更快,降低 LCP 值
减少 JS 阻塞时间的方法:
虽然 CSS 或 JavaScript 阻塞时间的增加会直接导致性能下降,但加载许多其他类型资源所需的时间也会影响绘制时间。
影响 LCP 的元素有以下几种:
优化方法:
如 React 、 Vue 、 Angular 这类框架所搭建的单页面应用,是完全在客户端中处理逻辑的。
优化方法:
TBT: Total Blocking Time 总阻塞时间,是页面被阻塞响应用户交互的总时间。 TBT = LCP (首次最大内容绘制)和可交互时间之间所有长时间任务的阻塞部分之和。是测量页面加载响应的重要指标。
超过 50 毫秒的任务即为长任务。 超出 50 毫秒的时间量为阻塞部分。
例如:检测到一个 90 毫秒的任务,则阻塞部分为 40 毫秒(90 - 50 = 40)
TBT 指标:
优化方法:
CLS: 累计布局偏移(CLS)是测量视觉稳定性的重要指标。是整个页面声明周期内发生的所有意外布局偏移中最大一连串的布局偏移分数。
页面内容的意外偏移大多是由于异步资源加载,或者动态添加 DOM 元素到页面现有内容上方导致的。罪魁祸首可能是未知尺寸的图像或视频、实际渲染后比后备字体更大或更小的字体等。
CLS 指标:
注意: 只有当现有元素的起始位置发生变更时才算做布局偏移。如果将新元素添加到 DOM 或是现有元素更改大小,则不算做布局偏移。只有当元素的变更会导致其他可见元素的起始位置发生变化,才叫偏移。
计算公式: 布局偏移分数 = 影响分数 x 距离分数
影响分数: 前一帧和当前帧的所有不稳定元素的课件区域集合(占总可视区域的部分)就是当前帧的影响分数。
距离分数: 指的是任何不稳定元素在一帧中位移的最大距离(水平或垂直)除以可视区域的最大尺寸维度(宽度或高度,以较大者为准)。
产生 CLS 的常见原因:
优化方法:
以上五个指标就是目前前端性能指标考量点,以及产生问题原因,优化方法等。每个优化点都可以扩展出很多知识以及学习点,所以前端优化这个工作链路依然很长;单一点的优化效果可能并不明显,但五个点全部优化,定然会有质的飞跃。
在实际项目中,优先从前端自身出发,优化完自身后,再去优化协同项。
另外前端优化是一件可持续,并长久的事情,工具技术升级的迭代也会提升项目性能,针对优化这样的工作一定要持续下去,而不是做一次就OK了。
前端性能优化这条路,道阻且长,行则将至,专研就会有进步,最终定然会成功达到目标。
前言 前段时间跟一位前辈聊到前端职业发展该怎么去规划这个问题。他说到的其中几个点我觉得非常好: 第一是要有清晰的自我认知,知道自己在一个团队或者在一个项目中能发挥怎样的价值,不骄傲自大也不要妄自菲薄;