页面的性能指标
-
DCL(DOMContentLoaded),DOM解析完毕。
-
FP(First Paint),表示渲染出第一个像素点。FP一般在HTML解析完成或者解析一部分时候触发。
-
FCP(First Contentful Paint),表示渲染出第一个内容,这里的“内容”可以是文本、图片、canvas。
-
FMP(First Meaningful Paint),首次渲染有意义的内容的时间,“有意义”没有一个标准的定义,FMP的计算方法也很复杂。
-
LCP(largest contentful Paint),最大内容渲染时间。
白屏时间(FP) = 地址栏输入网址后回车 - 浏览器出现第一个元素
首屏时间(FCP) = 地址栏输入网址后回车 - 浏览器第一屏渲染完成
如何了解当前页面的性能状况
- 通过Chrome浏览器的Lighthouse和performance功能面板查看资源数据 【performance面板的操作流程】
- 引入Google Chrome官方的 web-vitals 包进行数据搜集
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;
- 为了更准确的收集性能数据,可以通过performance API获取到响应的指标值。
白屏
一般是认为DOM Tree构建时,解析到的时候,我们认为是白屏结束的时间点。
我们可以在这个时候使用performace.mark进行打点标记,最后可以通过performance的
entry.startTime来获取白屏时间,其中entry.startTime是相对于performance.timing.navigationStart的时间。
<head>
...
<script>
// 通常在head标签尾部时,打个标记,这个通常会视为白屏时间
performance.mark("first paint time");
</script>
</head>
<body>
...
<script>
// get the first paint time
const fp = Math.ceil(performance.getEntriesByName('first paint time')[0].startTime);
</script>
</body>
首屏
一般是首屏中的图片加载完毕的时候,我们认为是首屏结束的时间点。我们可以对首屏中的image做onload事件绑定,
performace.mark进行打点标记,不过打点前先进行performance.clearMarks清除操作,以获取到多张图片最后加载完毕的时间。
<body>
<div class="app-container">
<img src="a.png" onload="heroImageLoaded()">
<img src="b.png" onload="heroImageLoaded()">
<img src="c.png" onload="heroImageLoaded()">
</div>
<script>
// 根据首屏中的核心元素确定首屏时间
performance.clearMarks("hero img displayed");
performance.mark("hero img displayed");
function heroImageLoaded() {
performance.clearMarks("hero img displayed");
performance.mark("hero img displayed");
}
</script>
...
...
<script>
// get the first screen loaded time
const fmp = Math.ceil(performance.getEntriesByName('hero img displayed')[0].startTime);
</script>
</body>
此外,Google也提供了一些新的API——PerformanceObserver
,来获取相应的指标值。
首次绘制 (FP)/首次内容绘制 (FCP)
PerformanceObserver
为我们提供的新功能是,能够在性能事件发生时订阅这些事件,并以异步方式响应事件。
let perfomanceMetrics = {};
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// `entry` is a PerformanceEntry instance.
// `name` will be either 'first-paint' or 'first-contentful-paint'.
const metricName = entry.name;
const time = Math.round(entry.startTime + entry.duration);
if (metricName === 'first-paint') {
perfomanceMetrics.fp = time;
}
if (metricName === 'first-contentful-paint') {
perfomanceMetrics.fcp = time;
}
}
});
页面的性能优化
渲染层面
- 优化文件资源的异步加载及处理顺序,不要阻塞页面的渲染
- 通过服务端渲染同构直出
(路由同构,数据同构,渲染同构)
省去了客户端二次请求数据的网络传输开销,服务端的网络环境要优于客户端,内部服务器之间通信路径也更短。首屏加载时间(FCP)更快,同时也有更好的 seo。
缓存层面
- 离线数据可以缓存到localStorage
- webpack配置chunkhash/contenthash实现资源依赖长期缓存(强缓存/协商缓存)
chunkhash根据两个chunk自身的内容,通过某种算法来生成各自的hash,不会相互影响。
contenthash的意思是hash的生成,是基于自身内容来创建的。css chunk和app chunk的hash因为是基于各自内容的生成的,现在他们hash是不一样的。修改任何一方都不会影响另外一方。
【webpack4配置实现浏览器长期缓存 - 知乎】
请求层面
(1)gzip可以有效压缩文件资源的体积
(2)雪碧图合并图片资源
(3)对于图片的处理,可以按支持情况使用webp/avif格式,且进行图片懒加载
(4)webpack打包时利用插件优化资源大小
url-loader(将小图片转成base64打包进 html 中,减小图片数量);
terser-webpack-plugin、uglifyjs-webpack-plugin(压缩 js 文件);
webpack SplitChunksPlugin (更合理的拆包策略)【查看更多】
(1)升级HTTP2.0,利用多路复用让请求可以并行
(2)图片等静态资源上传到CDN服务【CDN加速原理】
(3)配置DNS预解析【DNS预解析详解】
①用meta信息来告知浏览器, 当前页面要做DNS预解析 <meta http-equiv="x-dns-prefetch-control" content="on">
②在页面header中使用link标签来强制对DNS预解析: <link rel="dns-prefetch" href="//www.zhix.net">
内嵌的webview网页优化
服务端渲染直出html后仍然需要加载一个html文件,离线包基本思路通过通过webview统一拦截url, 将资源映射到本地离线包【事先打包好的文件资源】, 更新的时候对版本资源检测, 下载和维护本地缓存目录中的资源
打包构建的性能优化
- 使用 webpack-bundle-analyzer 查看打包出来文件的体积大小,看看是哪个包太大了。
- 在排除了包大小的问题之后,再使用 speed-measure-webpack-plugin 插件来查询是哪个插件或者是 loader 整慢了。
- 发现有些插件的运行确实慢了,但是我们也没精力去改人家的代码,所以还是牺牲自己电脑的内存吧,比较常见的方案就是使用 happypack / thread-loader来进行多进程构建。(原理类似,每次 webpack 解析一个模块,happypack / thread- loader 会将它及它的依赖分配给 worker 线程中,从而达到多进程打包的目的。)
- 生产环境开启 tree shaking 去掉没有引用的代码。
- 引入 dllplugin+dllReferencePlugin 动态链接库方案,将第三方库单独打包,再链入我们的webpack项目中。(对于现代化前端,我们总是会应用各种库,这些库我们并不会频繁去变动他们,为了避免每次都是构建这些不常变动的代码,可以把这些代码抽离出来,webpack中,我们可以结合DllPlugin 和 DllReferencePlugin插件来实现,在实操中还是用 polyfill 来进行引入对应库的静态资源,连打包都省掉了,只用使用 externals 来引入这些我们埋在 html 的 js 资源。)
- 其实在构建过程主要是 js 的编译这块可能会占用很多的时间来构建,所以 babel-loader 的 cache 属性来配置缓存或者直接使用 cache-loader 来进行缓存来提升二次构建速度。
- 通过 rule 的 include 属性来缩小构建目标,resolve 的具体配置来减少文件搜索范围。
其他体验优化
- 渲染长列表使用虚拟列表方案 【了解更多】
- web worker 处理复杂的计算【了解更多】
- 避免重绘回流过多导致的动画性能问题【了解更多】
参考文章
离线包方案
APP 内嵌 h5 性能优化
前端优化之DNS预解析
前端性能检测工具 --- Web Vitals
Web前端最新性能优化
前端页面性能指标与采集方式