用户打开页面的前 1-3 秒,决定了他们是留下来继续浏览,还是直接关掉走人。首屏优化要解决的核心问题就是:让用户尽可能快地看到有意义的内容,而不是一片空白。
白屏产生的原因
在 Vue 应用(以及其他 SPA)中,白屏是指用户从点击链接到页面渲染出内容之前,看到的长时间空白阶段。主要原因包括:
- HTML 文档本身为空或只有
<div id="app">
浏览器下载 HTML 后,发现它只是一个挂载容器,没有任何可见内容。必须等待 JS 下载、解析、执行完毕,Vue 实例挂载后才会绘制 DOM——这段时间就是“白屏时间”。
- JS 包体积过大
首屏需要一次性加载包含 Vue、Router、组件库、业务代码在内的巨大 chunk,下载与解析耗时极长。
- 网络传输慢
在没有 CDN 加速、未开启压缩或弱网环境下,资源拉取成为瓶颈。
- 渲染阻塞
CSS 或 JS 的同步加载阻塞了首次渲染。
简单概括:白屏的本质是“浏览器在等数据与代码准备就绪”,期间可展示的内容为零。
白屏优化策略
优化的核心思路是:缩短“代码下载与执行”的时间,同时尽早绘制出一些静态内容,让用户感知到页面正在加载,而不是“卡死了”。
1. 减轻首屏 JS 体积
- 路由懒加载
使用动态 import() 将各路由对应的组件拆分为独立 chunk,首屏只加载当前路由所需的代码。
// router/index.js
const Home = () => import('@/views/Home.vue')
const About = () => import('@/views/About.vue')
- 组件、库的按需引入与异步加载
对于 Element Plus、Ant Design Vue 等 UI 库,使用官方提供的按需引入插件(如 unplugin-vue-components),只打包实际用到的组件。对于统计 SDK、富文本编辑器等首屏非必需的功能,使用异步组件或使用时才动态加载。
- 精细化代码分割
将大型工具库(如 Moment.js 或 ECharts)拆成独立 chunk,避免阻塞首屏业务逻辑的下载。
2. 资源加载优化
- Gzip / Brotli 压缩
服务器开启静态资源压缩后,JS 和 CSS 体积可缩减 70% 以上,显著减少传输时间。
- CDN 加速
将静态资源部署到 CDN 节点,用户从物理最近节点拉取,降低延迟。对于 Vue、Vue Router、Axios 等可以配置 externals 直接使用 CDN 版本,减少打包体积。
- 预加载关键资源
在 index.html 中通过 <link rel="preload"> 或 <link rel="modulepreload"> 提前加载字体、首屏所需的 CSS 或核心 JS chunk,告知浏览器“这个资源很重要,优先去拿”。
- 现代与传统双包分发
Vite 支持同时生成 ESM 版本(给现代浏览器)和 Legacy 版本(给老浏览器)。现代浏览器只需下载体积小、语法更高效的 ESM 代码,加载速度更快。
3. 尽早渲染内容
- 使用
v-cloak与初始状态占位
在挂载容器中预先写入一个简单的加载状态或静态骨架,并配合 v-cloak 样式,让它在 Vue 完成挂载前可见,挂载后隐藏。
<!-- index.html -->
<div id="app">
<div class="initial-loading">加载中...</div>
</div>
<style>
[v-cloak] { display: none; }
</style>
- SSR / SSG
对于内容型网站或需要 SEO 的场景,使用 Nuxt 等服务端渲染方案,直接在服务端产出带完整 HTML 结构的页面,用户第一帧就能看到内容,彻底消除白屏。
骨架屏:从“加载中文字”到“结构占位”
骨架屏是一种介于白屏与完整内容之间的中间状态,它用灰色块占位,模拟最终页面的布局结构,给用户“页面正在加载中、内容即将出现”的心理预期,降低跳出率。
实现方式对比:
| 方案 | 做法 | 优点 | 缺点 |
|------|------|------|------|
| 手写骨架组件 | 为每个需要骨架的页面单独编写一个骨架组件,使用占位形状(矩形、圆形、线条)与动画。 | 灵活,可与真实组件结构对应,还原度高。 | 开发工作量大,需要与页面结构保持同步更新。 |
| 自动生成骨架屏 | 使用 vue-skeleton-webpack-plugin 或 draw-page-structure 等工具,在构建时自动提取页面结构并生成骨架片段的 HTML 或 Vue 组件。 | 自动化程度高,无需手写大量占位代码。 | 对动态列表、不规则布局的支持有限,可能需要手动修复边缘情况。 |
| UI 库内置骨架 | Element Plus、Ant Design Vue 等均提供 <Skeleton> 组件,可直接组合出骨架。 | 集成方便,样式统一。 | 仍需手动组合,且与自身业务组件的对应需要人为保证。 |
推荐实践:
- 首屏优先使用 SSR/SSG 解决白屏,骨架屏作为 CSR 模式的补充。
如果已使用 Nuxt 做 SSR,首屏无白屏问题,骨架屏可用于客户端跳转后的次级页面加载。
- 手写骨架组件与真实组件强绑定。
比如 ProductList.vue 组件旁放一个 ProductListSkeleton.vue,内部布局完全一一致,只是用灰色占位替换数据。在异步请求期间渲染骨架组件,数据到达后切换为真实组件。
<template>
<Suspense>
<template #default>
<ProductList :data="list" />
</template>
<template #fallback>
<ProductListSkeleton />
</template>
</Suspense>
</template>
- 使用 CSS 渐变动画让骨架“动起来”,强化加载反馈。
@keyframes skeleton-loading {
0% { background-position: -200px 0; }
100% { background-position: calc(200px + 100%) 0; }
}
.skeleton-box {
background: linear-gradient(90deg, #eee 25%, #f5f5f5 50%, #eee 75%);
background-size: 200px 100%;
animation: skeleton-loading 1.5s infinite;
}
核心指导原则:
- 首屏优化没有银弹,通常需要缩小体积 + 减少阻塞 + 尽早填充内容三管齐下。
- 白屏时间超过 1 秒就应该考虑优化。
- 骨架屏的目的是降低用户感知到的等待焦虑,而不是替代加载速度——最终仍需要持续缩减资源体积、提升网络效率,让真实内容尽快展示。