本文深入解析JS首屏加载时间的优化策略,涵盖代码分割、资源预加载、CSS与JS加载优化及DNS预解析。通过Webpack SplitChunks、React.lazy、preload/prefetch及关键CSS提取等技术,显著减少HTTP请求与渲染阻塞,提升网页性能与用户体验。
首屏加载时间是评估网页性能与用户体验的核心指标,在面试与项目实践中占据重要地位。它定义为从用户发起请求到网页第一屏内容完全渲染并可视的时间。首屏指用户未滚动页面时可见的区域,通常是访问网站或应用时最先展示的信息。
首屏加载速度直接影响用户的第一印象与留存率。用户对加载缓慢的网站耐心有限,若首屏加载时间过长,易引发不耐烦情绪,导致用户在内容加载完成前离开,从而增加跳出率并降低用户体验。
白屏时间指从用户发起页面请求(如点击链接或输入网址)到页面出现第一批可视化内容(非完全空白)之间的时长。此期间用户面对空白屏幕,故称“白屏时间”。它主要受网络速度、服务器响应时间及初始渲染资源大小影响。
代码分割
在结合 Webpack 和 React 的项目中,代码分割(Code Splitting)是优化首屏加载时间的重要手段。它将大型 bundle 文件拆分为多个较小的 chunks(块),实现按需加载,从而减少首次加载时间并加速首屏显示。
Webpack 的 SplitChunksPlugin 可用于自动分割公共模块和第三方库。通过合理配置 optimization.splitChunks 选项,可将公共依赖提取到独立 chunk 中,避免重复打包,减小首屏加载体积。
optimization: {
splitChunks: {
chunks: 'all', // 可以是`async`(仅分割异步加载模块),`initial`(仅分割初始加载模块),或`all`(两者都分割)
minSize: 20000, // 生成chunk的最小体积(以字节为单位)
minChunks: 1, // 在分割之前,模块被共享的最少次数
maxAsyncRequests: 30, // 按需加载时的最大并行请求数
maxInitialRequests: 30, // 入口点的最大并行请求数
automaticNameDelimiter: '~', // 默认情况下,webpack将使用块的来源和名称生成名称(例如vendors~main.js)
cacheGroups: { // 缓存组可以继承和/或覆盖splitChunks.*的任何选项
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
在 React 中,可利用 import() 语法实现动态导入,Webpack 会自动将这些模块分割为新 chunk。对于 React 组件,可结合 React.lazy 进行按需加载。
const LazyComponent = React.lazy(() => import("./LazyComponent"));
使用 Webpack 的魔法注释可实现更细致的控制,例如命名 chunk、预加载和预获取。
const LazyComponent = React.lazy(() =>
import(/* webpackChunkName: "lazy-component" */ "./LazyComponent")
);
// 预加载
const AnotherComponent = React.lazy(() =>
import(/* webpackPrefetch: true */ "./AnotherComponent")
);
// 预获取
const YetAnotherComponent = React.lazy(() =>
import(/* webpackPreload: true */ "./YetAnotherComponent")
);
为动态导入的组件提供加载状态,可使用 Suspense 组件包裹懒加载组件,并指定 fallback 加载指示器。
对于使用 React Router 等路由库的应用,可在路由配置中应用代码分割,为每个路由页面实现懒加载。
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import React, { Suspense, lazy } from "react";
const Home = lazy(() => import("./routes/Home"));
const About = lazy(() => import("./routes/About"));
const App = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
</Switch>
</Suspense>
</Router>
);
优化资源加载
优化资源加载是提升网页性能、尤其是首屏加载速度的关键。资源包括 JavaScript、CSS、图片、字体等。优化这些资源的加载可显著改善用户体验。
减少请求次数
将多个小的 JavaScript 或 CSS 文件合并为一个文件是常见的前端性能优化策略。此举可减少 HTTP 请求次数,从而提高页面加载速度和用户体验。
为什么要合并文件:
- 减少 HTTP 请求:每个文件请求涉及一定开销(如 DNS 查找、TCP 握手等),尤其在 HTTP/1.x 协议下,这些开销显著影响加载性能。合并文件可减少请求次数,降低总体开销。
- 提高缓存效率:将多个文件合并为一个,可提高浏览器缓存利用率。用户首次访问加载合并文件后,再次访问或使用相同合并文件时,可直接从缓存加载,无需重新发起请求。
合并文件时需注意文件间的依赖关系,确保合并后文件执行时保持正确依赖顺序。对于无需立即执行的 JavaScript 模块或 CSS 文件,可考虑异步加载,避免阻塞页面渲染。
若服务器支持 HTTP/2,可利用其多路复用功能同时传输多个请求,减少合并文件的必要性。但在 HTTP/2 环境下,合理的文件合并仍有优势,尤其在缓存管理和初始加载性能方面。
对于图片,可将多个小图片合并到一张大图集中,并通过 CSS 背景定位展示特定部分,此技术称为 CSS 雪碧图(CSS Sprites)。它有效减少图片请求次数,提升页面加载性能。另一种手段是将小图标或小图片转换为 Base64 编码格式并嵌入 HTML 或 CSS 中,进一步减少 HTTP 请求。但需注意,Base64 编码会使图片体积增大约 33%,因此更适用于较小图片。这两种技术均为前端性能优化常用方法,旨在加快渲染速度并提升用户体验。
使用现代图片格式
WebP 和 AVIF 是较新的图像格式,相比传统 JPEG 和 PNG 格式,它们提供更高压缩效率,意味着在保持相似图像质量时文件更小。这主要得益于这些格式采用的先进编码技术。
webp
先进压缩算法:WebP 格式使用基于 VP8 视频编解码器的压缩技术。该技术针对图像内容进行更高效压缩,通过减少编码图像块位数来减小文件大小,同时尽量保持图像质量。
支持无损和有损压缩:WebP 既支持无损压缩,也支持有损压缩。无损压缩 WebP 图像通常比 PNG 小 30%,而有损压缩 WebP 图像比 JPEG 小 25-34%,且可提供相同甚至更好质量。
丰富颜色表现:WebP 支持更广颜色范围和深度,包括 8 位或 10 位色深的有损编码和 8 位色深的无损编码,使其在色彩表现上优于标准 JPEG 格式。
集成额外功能:如支持透明度(Alpha 通道)和动画,使 WebP 成为非常灵活的图像格式,可替代 GIF 等格式。
AVIF
基于 AV1 视频编解码器:AVIF 是一种新图像格式,基于开源 AV1 视频编解码器。AV1 由联盟视频联盟开发,旨在网络上提供高效视频流传输。
更高效压缩性能:AVIF 提供比 WebP 更高压缩效率,尤其在高分辨率和高质量图像上。在相同视觉质量下,AVIF 文件大小通常比 WebP 更小。
更好图像质量:AVIF 支持更高动态范围和颜色深度,包括 HDR(高动态范围图像)和广色域图像,意味着它可提供更丰富色彩和更细腻图像细节。
更多编码特性:AVIF 支持多种编码特性,如 10 位和 12 位色深、4:4:4、4:2:2 和 4:2:0 色度子采样等,为图像提供更多灵活性和选择。
总之,WebP 和 AVIF 能在保持相同图片质量时减少文件大小,主要因其采用更高效压缩算法和先进编码技术。这些技术使 WebP 和 AVIF 能更好减少冗余信息,从而在不牺牲图像质量前提下降低文件大小。这些特性使 WebP 和 AVIF 非常适合用于网络图像,以提高网页加载速度和改善用户体验。
预加载关键资源
在现代网页性能优化中,<link rel="preload"> 和 <link rel="prefetch"> 是两种重要的资源提示(Resource Hints),它们能帮助开发者控制浏览器资源加载优先级,从而优化用户体验。
<link rel="preload">
<link rel="preload"> 是一种告诉浏览器预先加载页面初始化时即需资源的方法。这些资源对当前页面必要,但可能因 HTML 解析顺序或延迟加载策略不会立即加载。通过preload,开发者可显式告知浏览器尽早加载这些关键资源,确保及时使用,从而减少渲染阻塞时间,加快首屏渲染速度。
<!-- 预加载关键的CSS文件 -->
<link rel="preload" href="important.css" as="style" />
<!-- 预加载关键的JavaScript文件 -->
<link rel="preload" href="main.js" as="script" />
<!-- 预加载字体文件 -->
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin />
在使用preload时,as属性非常重要,因为它指定被加载资源类型,有助于浏览器正确优先加载资源,并使用正确加载策略。
<link rel="prefetch">
<link rel="prefetch"> 是另一种资源提示,指示浏览器在空闲时间预先获取用户将来可能需要的资源。这通常适用于预加载下一页资源,如用户很可能点击链接的资源。prefetch优先级较低,不影响当前页面关键资源加载,因此主要用于提升未来页面加载速度。
<!-- 预获取下一页面可能需要的CSS文件 -->
<link rel="prefetch" href="next-page-style.css" />
<!-- 预获取下一页面可能需要的JavaScript文件 -->
<link rel="prefetch" href="next-page-script.js" />
<link rel="preload">:用于当前页面关键资源,目的是尽快加载这些资源以提升当前页面性能。适用于对当前页面渲染至关重要的资源,如关键 CSS 和 JavaScript 文件、字体文件等。<link rel="prefetch">:用于将来页面资源,目的是利用浏览器空闲时间提前获取可能需要的资源,以提升未来页面加载速度。适用于用户可能接下来访问的页面资源,如下一篇文章脚本、样式表或图片等。
通过合理利用这两种资源提示,开发者可更好优化资源加载顺序和时间,从而改善用户体验。但需注意,过度使用或不当使用这些技术可能带来资源竞争或浪费带宽,因此应根据实际情况谨慎选择使用。
优化 CSS 加载
优化 CSS 加载是提升页面渲染速度和用户体验的重要方面。主要包括两个策略:处理关键 CSS(Critical CSS)和异步加载非关键 CSS。
关键 CSS 是指用于首屏渲染的最小 CSS 集合,即页面加载初始阶段用于样式化内容的 CSS。将关键 CSS 内联在 HTML 文档的<head> 部分中,可减少阻塞渲染的外部样式表数量,从而加快首屏内容显示速度。
关键 CSS
在使用 Webpack 构建工具时,可通过特定插件和加载器自动提取关键 CSS,以优化页面加载性能。以下是在 Webpack 环境中提取关键 CSS 的常用方法和步骤:
使用 html-webpack-plugin 和 critters-webpack-plugin:
- 安装相关插件 critters-webpack-plugin 和 html-webpack-plugin。
- 在 webpack.config.js 文件中,引入这两个插件并将它们添加到 plugins 数组中。critters-webpack-plugin 会处理 html-webpack-plugin 生成的 HTML 文件,提取并内联关键 CSS。
const HtmlWebpackPlugin = require("html-webpack-plugin");
const Critters = require("critters-webpack-plugin");
module.exports = {
// 其他Webpack配置...
plugins: [
new HtmlWebpackPlugin({
template: "src/index.html",
}),
new Critters({
// Critters的配置选项...
}),
],
};
对于更复杂场景,如需要进一步优化并清理未使用 CSS 时,可结合使用 mini-css-extract-plugin 和 purifycss-webpack。
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const PurifyCSSPlugin = require("purifycss-webpack");
const glob = require("glob");
module.exports = {
// 其他Webpack配置...
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].[contenthash].css",
}),
new PurifyCSSPlugin({
paths: glob.sync(path.join(__dirname, "src/**/*.html")),
minimize: true,
}),
],
};
非关键 CSS
非关键 CSS 是指对首屏渲染非必需的 CSS,如用于页面下方内容或其他页面的样式。异步加载这些非关键 CSS 可确保它们不阻塞首屏渲染。
主要有以下几种实现方法:
使用标签的 media 属性:通过设置 media 属性为不匹配媒体查询,可使浏览器延迟加载 CSS 文件。页面加载完成后,通过 JavaScript 更改 media 属性为 all 来加载和应用样式。
<link
rel="stylesheet"
href="non-critical.css"
media="print"
onload="this.media='all'"
/>
<noscript><link rel="stylesheet" href="non-critical.css" /></noscript>
使用 JavaScript 动态加载 CSS:通过 JavaScript 动态创建 <link> 标签并插入到 <head> 中,可实现 CSS 异步加载。
var link = document.createElement("link");
link.rel = "stylesheet";
link.href = "non-critical.css";
document.head.appendChild(link);
减少 CSS 选择器复杂性:避免使用过于复杂 CSS 选择器,因它们会增加浏览器样式计算时间。
避免使用@import:@import 允许在 CSS 文件中导入其他 CSS 文件,但会增加页面加载时间,因浏览器需解析第一个 CSS 文件后才能发现并加载导入文件。
优化 JavaScript 加载
使用 async 和 defer 属性异步加载 JavaScript,减少阻塞渲染脚本。
当脚本带有 async 属性时,它将并行于页面加载进行下载。一旦脚本下载完成,它将立即执行,无需等待其他脚本或页面渲染完成。这对于不依赖其他脚本且不被其他脚本依赖的功能是不错选择。
<script async src="script.js"></script>
带有 defer 属性脚本会等到整个页面解析完毕后才会执行,且按它们在文档中出现顺序依次执行。这通常用于需访问或修改 DOM 脚本,且不影响文档初始显示。
<script defer src="script.js"></script>
DNS 预解析
DNS 预解析是一种性能优化技术,用于减少用户浏览网页时遇到的延迟。当用户访问网页时,浏览器需将网页域名(如 example.com)转换为 IP 地址,此过程称为 DNS 解析。DNS 解析需时间,特别是当 DNS 查询需通过多个网络节点时,该时间可能显著影响网页加载速度。通过使用 DNS 预解析,可提前进行此解析过程,从而减少用户等待时间。
DNS 解析可能需要一定时间,特别是若 DNS 服务器距离用户较远,或需多次跳转才能找到相应记录时。通过预解析,可在用户实际请求资源前完成此步骤,从而减少等待时间。
DNS 预解析可通过在 HTML 中添加标签来实现,如下所示:
<link rel="dns-prefetch" href="//www.难道你是个天才?.com" />
DNS 预解析对于那些含有大量来自不同域名资源网页尤为有用,如外部脚本、广告、图片等。
其他的一些方案
使用 CDN 是较常见解决方案,将资源部署到 CDN 上,可使资源从用户地理位置最近服务器加载,减少延迟。
另一个是使用 HTTP/2,因 HTTP/2 提供头部压缩、服务器推送、请求复用等特性,相较于 HTTP/1.1 可显著提升资源加载效率。
编写高性能且易维护代码极其重要,例如减少回流和重绘等。(之前实习看到一个同事在一个 CSS 文件上写了 3000 多行样式,看的时候还是在开发 ing...)
终极解决方案
让用户换电脑,如果响应慢,肯定是电脑出了问题,我的建议是换成顶配的 mac。(难道你是个天才?)
总结
通过实施上述策略,可优化资源加载过程,提升网页加载速度和用户体验。值得注意的是,每个项目具体情况不同,因此在实施这些优化时应根据实际需要和测试结果灵活调整。
到此这篇关于JS首屏加载时间优化的解决方法总结的文章就介绍到这了,更多相关JS首屏加载时间优化内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!