首屏加载慢

本文介绍了如何通过分析`performance.timing`对象来评估首屏加载时间,探讨了关键性能指标如navigationStart、DOMContentLoaded和loadEventEnd。文章提供了优化策略,如异步加载脚本、减少入口文件体积、静态资源缓存、按需加载组件和图片压缩,以及GZip压缩和服务器端的Http2优化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 查看首屏加载时间

performance.timing 是浏览器提供的一个对象,它包含了网页加载过程中各个关键阶段的 时间戳 数据,这些数据对于分析网页性能、识别加载瓶颈非常有用。

首屏加载时间,指的是 浏览器从相应用户输入网址,到首屏内容渲染完成的时间
此时整个网页不一定全部渲染完成,但需要展示当前视窗需要的内容;
首屏加载是用户体验的重要环节。

控制台输入

(performance.timing.domComplete - performance.timing.navigationStart) / 1000
// 2.043,表示首屏加载时间是2.043秒

performance.getEntrier() // 查看每一个资源加载的时间
  • navigationStart:表示浏览器开始加载当前文档的起始时间,包括重定向开始的时间。整个导航计时的基准点。
  • domLoading:开始解析HTML文档并构建 DOM 树的时间。
  • domComplete:记录所有资源(包括图像、样式表、脚本等)加载完毕,DOM树构建完成,且所有 load 事件处理程序待触发的时间。此时 document.readyState 变为 "complete"
  • domContentLoadedEventEnd:“DOMContentLoaded” 事件实际触发的时间,此时浏览器已经完成了DOM解析和CSSOM构建,大部分JavaScript脚本可以开始执行
  • loadEventStart: “load” 事件即将被触发的时间,标志着所有资源(包括子框架)已完全加载。
  • loadEventEnd:“load” 事件实际触发并执行完成的时间,网页加载过程至此全部结束。
  • connectStart:开始建立与服务器TCP连接的时间。这包括SSL/TLS握手(如果有)的时间。
  • connectEnd:完成与服务器TCP连接建立的时间。此时,TCP连接已建立,可以开始发送HTTP请求。
  • requestStart:发送HTTP请求(包括请求头)到服务器的时间。至此,浏览器完成了所有前期准备工作。
  • responseStart:收到第一个字节的HTTP响应的时间。服务器开始返回数据。
  • TCP连接建立耗时:connectEnd - connectStart
  • 请求发送到接收响应首字节的耗时(网络往返时间): responseStart - requestStart
  • 页面渲染准备时间(DOMContentLoaded):domContentLoadedEventEnd - navigationStart
  • 完整页面加载时间(load事件):loadEventEnd - navigationStart

按照时间顺序
在这里插入图片描述

Object.entries(timing).sort(([, valueA], [, valueB]) => valueA - valueB);

[
	  [ 'navigationStart', 1713947661293 ], // 表示**浏览器开始加载当前文档的起始时间**,包括重定向开始的时间。整个导航计时的基准点。
	  [ 'redirectStart', 0 ], // 收到第一个字节的HTTP响应的时间。服务器开始返回数据。
	  [ 'redirectEnd', 0 ], // 收到完整HTTP响应(包括所有响应头部和正文)的时间。至此,服务器端的响应过程结束。
	  [ 'fetchStart', 1713947661294 ], // 记录浏览器开始为当前文档请求资源(如发起网络请求或从缓存中读取)的时间。如果资源来自缓存,这个时间可能早于 navigationStart。
	  [ 'domainLookupStart', 1713947661301 ], // 记录浏览器开始进行 DNS 解析(如果需要)的时间。如果使用持久连接、缓存或不需要 DNS 解析(如 file: 协议或同一源重新加载),则值等于 fetchStart。
	  [ 'domainLookupEnd', 1713947661301 ], // 记录 DNS 解析完成的时间。如果不需要 DNS 解析,则值等于 domainLookupStart。
	  [ 'connectStart', 1713947661301 ], // 开始建立与服务器TCP连接的时间。
	  [ 'secureConnectionStart', 1713947661331 ], // 如果当前连接使用了SSL/TLS协议,记录开始安全连接协商的时间。如果未使用SSL/TLS,值为0。
	  [ 'connectEnd', 1713947661368 ], // 完成与服务器TCP连接建立的时间。此时,TCP连接已建立,可以开始发送HTTP请求。
	  [ 'requestStart', 1713947661368 ], // 发送HTTP请求(包括请求头)到服务器的时间。至此,浏览器完成了所有前期准备工作。
	  [ 'responseStart', 1713947661411 ], // 收到第一个字节的HTTP响应的时间。服务器开始返回数据。
	  [ 'responseEnd', 1713947661413 ], // 记录浏览器接收到服务器响应的最后一个字节的时间,即整个响应接收完毕。
	  [ 'domLoading', 1713947661423 ], // 记录浏览器开始解析 HTML 文档并构建 DOM 树的时间。
	  [ 'domInteractive', 1713947661454 ], // 记录 DOM 树解析完成,脚本执行完毕,且页面处于交互状态(即 document.readyState 变为 "interactive")的时间。
	  [ 'domContentLoadedEventStart', 1713947661707 ], // 记录 DOMContentLoaded 事件开始触发的时间,此时DOM树已构建完成,样式表已解析完成,但外部脚本可能仍在加载和执行。
	  [ 'domContentLoadedEventEnd', 1713947661708 ], // 记录 DOMContentLoaded 事件处理程序执行完毕的时间。
	  [ 'domComplete', 1713947661728 ], // 记录所有资源(包括图像、样式表、脚本等)加载完毕(不包括可能仍在加载的非关键性资源),DOM树构建完成,且所有 load 事件处理程序待触发的时间。此时 document.readyState 变为 "complete"。页面处于可交互状态。
	  [ 'loadEventStart', 1713947661728 ], // 记录 load 事件开始触发的时间,标志着页面加载过程基本结束。
	  [ 'loadEventEnd', 1713947661759 ], // 记录 load 事件处理程序执行完毕的时间,标志着整个页面加载周期的正式结束。
]

2 解决方案

异步加载脚本

HTML解析器遇到script标签会暂停渲染过程,将控制权交给JS引擎;JS引擎运行完毕,浏览器把控制权还给渲染引擎,继续CSSOM和DOM的构建;(浏览器碰到JS就交出控制权的原因是不知道JS的内容会不会对接下来的渲染有没有影响)

  • 使用async属性让脚本非阻塞渲染流程异步加载;
<script async src="app.js"></script>
  • 使用 defer 属性让脚本非阻塞渲染流程异步加载;
<script defer src="app.js"></script>

async 和 defer 的区别是 async 是下载完就执行,加载完script.js,HTML解析暂停,不保证执行顺序。
defer 是在所有元素解析完成之后,再执行;DOMContentLoaded 事件触发之前完成,按照写的顺序执行;

2.1 减少入口文件体积(重要)

入口文件体积主要指的是HTML、CSS和JavaScript等资源文件的初始下载大小。
路由懒加载,解析路由时才会加载组件。

const routes = [
  {
    path: "Blogs",
    name: "ShowBlogs",
    component: () => import('./components/ShowBlog.js')
  }
];
文本压缩

移除注释、空白字符等非必要内容,减少文件大小;

// Webpack配置示例
optimization: {
  minimize: true,
}

2.2 静态资源本地缓存

  • 后端返回资源:采用http缓存;
  • 前端合理利用localStorage;
  • CDN(内容分发网络)来加速静态资源的加载:react、react-dom、react-router-dom、axios;

2.3 UI框架按需加载(重要)

  • element-UI、antd按需引入;
 import { Button } from 'antd';

2.4 避免组件重复打包(重要)

假设A.js文件是一个常用的库,有多个路由使用A.js文件,这样会造成重复下载;
解决方案:在webpack的config文件中,修改CommonsChunkPlugin的配置 minChunks: 2;
将使用2次及以上的包抽离出来,放进公共依赖文件中,避免重复加载组件;

2.5 图片资源压缩(重要)

  • 对于页面上使用的icon,可以使用在线字体图标,或者雪碧图,将众多的小图标合并到一张图上,减轻http请求的压力;
  • 使用png格式或者使用tinyPNG等工具压缩图片,在不影响视觉质量的前提下减小文件尺寸。
  • 这里要注意:图片懒加载并不能直接减少入口文件体积,但是可以减少首次页面渲染所需要的总下载量;<img src="image.jpg" loading="lazy" alt="描述文字" />
    通过推迟非首屏图片的加载时间,从而加速首页的渲染速度;具体体现在:
    • 减少初始请求量:减少HTTP请求;
    • 优先加载关键内容:确保核心内容可以被优先加载和渲染,而不会被图片的加载过程阻塞;
.sprite-container {
  background-image: url('path/to/sprite.png');
}

.icon-example1 {
  width: /* 图标宽度 */;
  height: /* 图标高度 */;
  background-position: /* X坐标 */px /* Y坐标 */px;
}

.icon-example2 {
  width: /* 图标宽度 */;
  height: /* 图标高度 */;
  background-position: /* X坐标 */px /* Y坐标 */px;
}

2.6 开启GZip压缩

拆完包之后,再用gzip做一下压缩,安装compression-webpack-plugin
webpack中配置压缩;

2.7 服务端优化(不属于首页加载,属于加载)

使用Http2协议来减少网络延迟和多路复用请求;

### Vue3 和 Vite 构建项目的加载优化 为了提升基于 Vue3 和 Vite 的项目加载速度,可以从以下几个方面入手: #### 1. **减少文件体积** 通过减小打包后的文件大小来加快资源下载的速度。以下是几种常见的做法: - 使用 Tree Shaking 技术移除未使用的代码[^1]。 - 对 UI 组件库启用按需加载功能,仅引入实际需要的部分。 ```javascript // 示例:Ant Design Vue 按需加载配置 (vite.config.js) import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; import AutoImport from 'unplugin-auto-import/vite'; import Components from 'unplugin-vue-components/vite'; export default defineConfig({ plugins: [ vue(), AutoImport({ /* 自动导入 */ }), Components({ /* 按需加载组件 */ }) ], }); ``` #### 2. **利用 CDN 加速** 将一些常用的第三方库托管到公共 CDN 上,从而减轻本地打包的压力并充分利用浏览器缓存机制。 ```html <!-- 示例:HTML 中引用外部 CDN --> <script src="https://cdn.jsdelivr.net/npm/vue@3"></script> ``` #### 3. **实现路由懒加载** 通过对不同模块实施动态导入的方式,使得只有当用户访问特定页面时才会去请求对应的脚本文件。 ```javascript // 示例:Vue Router 路由懒加载 const routes = [ { path: '/about', component: () => import('@/views/AboutView.vue') // 动态加载 About 页面 } ]; ``` #### 4. **采用图片懒加载技术** 借助 `vue-lazyload` 插件或其他类似的解决方案,在滚动至可视区域之前不立即渲染图像内容,以此降低初始 DOM 渲染负担[^2]。 ```javascript // 安装插件 npm install vue-lazyload --save // 主入口 main.js 或 setupPlugin.ts 添加初始化逻辑 import VueLazyLoad from 'vue-lazyload' app.use(VueLazyLoad, { preLoad: 1.3, error: require('./assets/error.png'), loading: require('./assets/loading.gif') }) ``` #### 5. **拆分 Vendor 文件** 默认情况下,所有依赖会被集中放置于单个 vendor chunk 中;然而这可能造成加载过大而影响体验。因此建议调整 rollupOptions 来分离各个独立包[^3]。 ```javascript // vite.config.js 设置多 chunks 输出策略 build: { rollupOptions: { output: { manualChunks(id) { if (id.includes('node_modules')) { return id.toString().split('node_modules/')[1].split('/')[0]; } }, }, }, }, ``` 以上措施能够有效改善 Vue3 + Vite 应用程序的启动表现,并显著缩短用户的等待感知时间。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值