# Vue怎么防止白屏添加首屏动画
## 前言:理解白屏问题的本质
在单页应用(SPA)开发中,白屏问题是一个常见的性能优化痛点。当用户首次访问Vue应用时,需要经历以下过程:
1. 下载HTML骨架
2. 加载JavaScript文件
3. 执行Vue初始化
4. 渲染真实DOM
这个过程可能需要数百毫秒甚至数秒时间,在这期间用户看到的将是空白页面(即"白屏")。本文将深入探讨如何通过技术手段解决这个问题,并实现优雅的首屏动画体验。
## 一、白屏问题的根本原因分析
### 1.1 浏览器渲染机制
现代浏览器的渲染流程包括:
- HTML解析 → CSSOM构建 → 渲染树生成 → 布局 → 绘制
### 1.2 Vue应用的特殊性
与传统服务端渲染不同,Vue SPA的渲染流程:
```mermaid
graph TD
    A[空白HTML] --> B[加载JS]
    B --> C[执行Vue]
    C --> D[生成DOM]
通过Chrome DevTools的Performance面板分析,主要耗时在: - 网络请求(特别是大型JS文件) - Vue实例初始化 - 组件递归渲染
在index.html中添加静态loading:
<div id="app-loading">
  <div class="spinner"></div>
  <p>应用加载中...</p>
</div>
<style>
  #app-loading {
    position: fixed;
    /* 居中样式 */
    animation: fadeIn 0.5s;
  }
  .spinner { /* 旋转动画实现 */ }
</style>
通过Vue mounted钩子移除:
new Vue({
  mounted() {
    const loading = document.getElementById('app-loading');
    loading && loading.remove();
  }
})
router.beforeEach((to, from, next) => {
  showLoadingAnimation();
  next();
});
router.afterEach(() => {
  hideLoadingAnimation();
});
使用prerender-spa-plugin:
// vue.config.js
const PrerenderSPAPlugin = require('prerender-spa-plugin');
module.exports = {
  configureWebpack: {
    plugins: [
      new PrerenderSPAPlugin({
        staticDir: path.join(__dirname, 'dist'),
        routes: ['/', '/about'],
        renderer: new PrerenderSPAPlugin.PuppeteerRenderer()
      })
    ]
  }
}
<!-- index.html -->
<div id="app">
  <div class="skeleton-header"></div>
  <div class="skeleton-body">
    <div class="skeleton-line" v-for="i in 5"></div>
  </div>
</div>
// skeleton.js
export default {
  name: 'Skeleton',
  template: `<!-- 骨架屏模板 -->`
}
// vue.config.js
const SkeletonWebpackPlugin = require('vue-skeleton-webpack-plugin');
module.exports = {
  configureWebpack: {
    plugins: [
      new SkeletonWebpackPlugin({
        webpackConfig: {
          entry: './src/skeleton.js'
        }
      })
    ]
  }
}
Nuxt.js方案示例:
npx create-nuxt-app my-project
关键配置:
// nuxt.config.js
export default {
  loading: '~/components/LoadingBar.vue',
  loadingIndicator: {
    name: 'circle',
    color: '#3B8070'
  }
}
/* 渐现动画 */
@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}
/* 骨架屏动画 */
.skeleton-shimmer {
  background: linear-gradient(
    to right,
    transparent 0%,
    rgba(255,255,255,0.6) 50%,
    transparent 100%
  );
  animation: shimmer 1.5s infinite;
}
安装lottie-web:
npm install lottie-web
组件封装:
<template>
  <div ref="lottieContainer"></div>
</template>
<script>
import lottie from 'lottie-web';
export default {
  props: ['animationData'],
  mounted() {
    this.anim = lottie.loadAnimation({
      container: this.$refs.lottieContainer,
      renderer: 'svg',
      loop: true,
      autoplay: true,
      animationData: this.animationData
    });
  }
}
</script>
关键优化点: - 使用will-change属性 - 优先使用transform和opacity - 减少重排操作 - 使用requestAnimationFrame
function animate() {
  // 使用硬件加速
  element.style.transform = `translateY(${progress}px)`;
  requestAnimationFrame(animate);
}
// vue.config.js
module.exports = {
  configureWebpack: {
    optimization: {
      splitChunks: {
        chunks: 'all',
        maxSize: 244 * 1024 // 244KB
      }
    }
  }
}
<head>
  <link rel="preload" href="/fonts/roboto.woff2" as="font">
  <link rel="prefetch" href="/assets/logo.png">
</head>
组件级懒加载:
const Home = () => import(/* webpackChunkName: "home" */ './views/Home.vue');
图片懒加载:
<img v-lazy="imageSrc" alt="">
使用web-vitals库:
import { getLCP, getFID, getCLS } from 'web-vitals';
getLCP(console.log);
getFID(console.log);
getCLS(console.log);
// 记录白屏时间
const start = performance.now();
window.addEventListener('load', () => {
  const timing = performance.now() - start;
  axios.post('/monitor', { type: 'white_screen', timing });
});
graph LR
    A[CDN] --> B[Nginx]
    B --> C[SSR]
    C --> D[Service Worker]
    D --> E[Skeleton]
    E --> F[Lazy Load]
// main.js
import App from './App.vue';
import store from './store';
import router from './router';
function bootstrap() {
  // 显示加载动画
  showLoading();
  
  Promise.all([
    store.dispatch('init'),
    router.isReady()
  ]).then(() => {
    const app = createApp(App);
    app.mount('#app');
    
    // 隐藏加载动画
    hideLoading();
  });
}
// 根据网络情况选择策略
if ('connection' in navigator) {
  if (navigator.connection.saveData) {
    loadLiteVersion();
  } else {
    bootstrap();
  }
} else {
  bootstrap();
}