前端页面性能优化(三)
前端页面性能优化之加载顺序与首屏体验
前两文我们分别从图片和静态资源与缓存的角度,讨论了如何让页面“更轻、更省流量”。但真正决定用户主观体验的,还有一个更关键的问题:
用户到底在什么时候,能看到“有内容的页面”和“可以操作的页面”?
本文章将重点围绕:
- 浏览器核心性能指标(FCP / LCP / TTI / CLS)
- 关键渲染路径与阻塞因素(CSS / JS / 字体)
- 首屏加载顺序设计(Critical CSS、骨架屏、接口策略)
- 懒加载与按需加载(图片、组件、路由)
一、首屏相关的核心指标
首先要知道,我们到底要“优化的是什么”。常见的几个核心指标:
- FCP(First Contentful Paint):首次内容绘制,用户第一次看到任何文本、图片、非白屏内容的时间。
- LCP(Largest Contentful Paint):最大内容绘制,视口中最大的内容块(通常是大图、主标题、主卡片)绘制完成的时间。
- TTI(Time to Interactive):可交互时间,页面已经可以稳定响应用户输入的时间。
- CLS(Cumulative Layout Shift):累计布局偏移,代表页面布局在加载过程中“抖动”的程度。
简单理解:
- 想要“白屏时间短” → 看 FCP。
- 想要“主要内容早点出来” → 看 LCP。
- 想要“点了就有反应” → 看 TTI。
- 想要“元素不要乱跳” → 看 CLS。
首屏优化,就是围绕这几个指标做“有针对性”的调整。
二、关键渲染路径与阻塞资源
要优化加载顺序,首先要理解浏览器是如何把 HTML 变成“可见页面”的。
一个极简版流程:
- 解析 HTML → 构建 DOM 树。
- 解析 CSS → 构建 CSSOM。
- 合并 DOM + CSSOM → 生成 Render Tree。
- 布局(Layout) → 绘制(Paint)→ 合成(Composite)。
在这个过程中,有几类资源会对“首屏渲染”产生关键影响:
- CSS:默认是渲染阻塞的。
- 同步 JS:可能阻塞 HTML 解析与渲染。
- 字体:影响文字绘制策略,可能导致空白或闪烁。
- 大图与首屏关键图片:会影响 LCP。
理解这点后,我们的目标可以具体化为:
让首屏必须要的资源尽快、优先、顺序合理地加载;让非首屏必须的资源尽量延后或懒加载。
三、CSS 加载优化:尽量“快且少”
1. Critical CSS:内联关键样式
对于首屏结构所依赖的关键 CSS,可以选择直接内联进 HTML,避免首屏等待外部 CSS 请求完成。
示例(简化):
1 | |
实践要点:
- 不要把全部 CSS都内联,这会导致 HTML 体积暴涨,影响后续缓存策略。
- 建议配合构建工具提取首屏关键 CSS,或手动控制“首页 Hero 区、导航条等骨干结构”的样式。
2. 拆分 CSS:路由级 / 功能级
传统做法是把所有页面的 CSS 打在一个包里,结果是:
- 首屏页面只用到 20% 的样式。
- 用户却要为 100% 的样式等待与下载。
更推荐的做法是:
- 基础样式(reset、主题色、通用组件)单独一个包。
- 各路由或大模块的 CSS 分包。
配合路由懒加载,可以实现:
- 访问
/home只下载home.css。 - 访问
/dashboard时再下载dashboard.css。
3. 避免使用 @import 链式引入
在 CSS 中使用:
1 | |
会增加额外的请求等待和解析开销。相比之下,直接在 HTML 中用多个 <link> 更好:
1 | |
更进一步的优化,就是在构建阶段把多个 CSS 合并 / 按需拆分,而不是在线上用 @import。
四、JavaScript 加载与执行优化:别阻塞渲染
JS 是现代 Web 的灵魂,但也是性能问题的重灾区。
1. defer / async:脚本加载方式选择
当我们在 <head> 中引入 JS 时,如果不做任何处理:
1 | |
浏览器会:
- 停止解析 HTML。
- 下载并执行脚本。
- 执行完再继续往下解析。
这就有可能严重阻塞首屏渲染。
更推荐的方式是:
- 对非必须在第一时间执行的脚本,加上
defer或async。
1 | |
区别简记:
defer:下载异步,按顺序执行,在 DOM 解析完成后执行,不阻塞渲染。async:下载异步,谁先下载完谁先执行,可能打乱顺序,适合不依赖 DOM、也不被其他脚本依赖的场景。
2. 代码拆分与路由懒加载
对 SPA 来说,常见问题是:
- 打包产物一个
app.js,体积非常大。 - 用户打开首页时,其实只用到了 30% 的代码。
改进方式:
- 按路由拆分:每个路由一个或多个 chunk。
- 按功能模块拆分:图表库、富文本编辑器、低频使用组件等单独拆出。
示例(以 Vue Router 为例,简化说明):
1 | |
这样:
- 首次打开
/,只下载 Home 页相关代码。 - 当用户访问
/about时,再按需加载 About 页脚本。
3. 减少首屏同步 JS 逻辑
很多时候,我们在入口脚本中做了“太多事情”:
- 页面初始化就拉取多组接口。
- 马上执行各种事件绑定与初始化逻辑。
- 甚至在首屏阶段就加载大量第三方 SDK。
优化思路:
- 把首屏**“必要的交互”和“必要的数据”**优先处理。
- 把非必要逻辑延后到
requestIdleCallback、首屏渲染后、用户首次交互之后等时机。
举个非常简单的示例:
1 | |
五、骨架屏与占位:减少“主观白屏时间”
有时候,接口响应的客观时间可能很难再压缩,但我们可以优化用户的“主观等待感受”。
1. 骨架屏(Skeleton Screen)
骨架屏的核心理念是:
与其让用户看到一片白,不如先展示一个“灰色框架”,暗示这里有内容正在加载。
例如:
- 对列表:显示若干条灰色条形块。
- 对详情页:显示标题条、图片占位、若干段落占位。
在实现上,可以:
- 使用纯 CSS + div 实现骨架结构。
- 或使用 UI 组件库内置的 Skeleton 组件(如 Ant Design、Element Plus 等)。
2. 图片占位与渐进加载
对大图、Banner 图等,可以:
- 先渲染一个低清晰度的缩略图(LQIP:Low Quality Image Placeholder)。
- 当高清图加载完成再平滑替换。
这样可以显著改善 LCP 体验,避免空白空间长时间占位。
六、懒加载:把“不急用”的东西往后放
懒加载的本质是:
只在“需要的时候”才加载对应资源。
1. 图片懒加载
现代浏览器已经原生支持 loading="lazy" 属性:
1 | |
对于首屏外的图片,我们可以:
- 手动添加
loading="lazy"。 - 或通过框架 / 组件库提供的 LazyImage 组件。
对更精细的控制,可以使用 IntersectionObserver:
1 | |
2. 组件懒加载 / 路由懒加载
这一部分与上文的 JS 分包关系紧密:
- 路由级别的懒加载:用户访问某路由时才加载对应组件代码。
- 组件级别的懒加载:例如富文本编辑器、图表、地图等大体积组件只在真的需要时才加载。
以 React 为例(简化说明):
1 | |
这样,首屏并不会加载 HeavyEditor 的代码和依赖,只有用户真正点开时才会触发加载。
七、接口策略与首屏数据获取
首屏渲染不仅依赖静态资源,还依赖 API 数据。常见的几种策略:
1. CSR:纯前端渲染 + 接口拉取
流程大致为:
- 浏览器下载 HTML(简陋占位)。
- 下载 JS / CSS。
- JS 执行后发起接口请求。
- 数据返回后再渲染内容。
问题:
- 网络链路长,首屏可见内容时间较晚。
- 对弱网、移动端不友好。
2. SSR:服务端渲染
SSR 的流程是:
- 服务端直接将“带数据的 HTML”渲染好返回。
- 浏览器几乎可以在 HTML 到达后就完成首屏内容展示。
- 客户端 JS 再“水合”(Hydration)绑定事件。
优势:
- FCP / LCP 通常都有明显改善。
- 对 SEO 更友好。
成本:
- 服务端压力更高。
- 开发模式更复杂,需要考虑同构、数据获取时机等。
3. SSG / 预渲染(Pre-render)
对于内容相对稳定的页面(如博客、文档、营销页),可以:
- 在构建阶段就生成好 HTML。
- 部署时直接以静态文件形式托管。
这样既保留 SSR 的首屏体验优势,又减轻了运行时服务器压力。
4. 混合策略与渐进增强
在实际项目中,经常会采用混合策略:
- 首屏关键页面(首页、主要业务入口)采用 SSR / SSG。
- 其他路由使用 CSR + 懒加载。
- 对复杂交互和数据实时性要求高的页面,再按需做优化。
八、避免布局抖动:优化 CLS
CLS(累计布局偏移)高,会让用户感觉页面“在脚底下乱跳”。
常见原因:
- 图片未设置宽高,加载后突然把后续内容挤下去。
- 广告、推荐位动态插入 DOM,导致布局重新排布。
- 字体加载导致文字宽度变化。
优化建议:
- 为图片/视频提前设置宽高或使用占位容器。
1 | |
- 对于异步内容(广告、推荐位),提前预留容器高度。
- 通过前文提到的字体优化(font-display / 子集化)减少字体引起的布局抖动。
九、首屏优化实践清单
最后给出一个可以直接作为“Checklist”的实践清单,你可以在项目中对照使用:
HTML / 结构
- 首屏骨架结构是否简单清晰?
- 是否有必要的骨架屏或占位?
CSS
- 是否为首屏提取了 Critical CSS?
- 是否避免了层层
@import? - CSS 是否按路由 / 模块拆分?
JavaScript
- 是否合理使用
defer/async? - 是否对路由和大体积组件做了懒加载?
- 首屏入口代码中是否存在可延后的逻辑?
- 是否合理使用
图片与资源
- 首屏大图是否做了压缩与格式优化(参考本系列(一))?
- 是否有首屏外图片的懒加载?
- 是否避免了大量首屏不必要的第三方脚本?
数据与渲染方式
- 是否评估过使用 SSR / SSG 的收益?
- 首屏接口是否进行了合并、裁剪或缓存?
布局稳定性
- 图片 / 广告位是否预留空间?
- 字体加载策略是否会导致布局大幅变化?
十、总结
本篇从 指标 → 关键路径 → 加载顺序 → 懒加载 / 骨架屏 → 渲染策略 的顺序,系统梳理了首屏体验优化的思路:
- 明确优化目标:FCP / LCP / TTI / CLS。
- 控制阻塞资源:CSS 与同步 JS 的加载与执行。
- 把首屏必须的资源前置,把非首屏资源懒加载。
- 用骨架屏与占位减少“主观白屏时间”与布局抖动。
- 结合 SSR / SSG 等技术手段,从根本上缩短首屏渲染链路。
有了这套体系,你就不再是“零散地优化某个点”,而是可以从整体架构的角度,设计出一套适合自己项目的首屏性能优化方案。