<think>我们已经在之前的讨论中给出了一个完整的加载动画实现方案。现在,用户希望在使用Vue3+TypeScript+Vite构建的H5页面中实现初始化加载动画。我们将结合引用中提到的Vite-Vue3-H5项目模板(引用[2][3])和Vite的特性(引用[1])来优化实现。
### 实现思路
1. **创建加载动画组件**:使用SVG和CSS动画实现一个流畅的加载动画。
2. **全局控制**:在应用初始化时显示加载动画,在应用准备完成后隐藏。
3. **结合Vite特性**:利用Vite的快速启动和热更新优势,确保加载动画在开发和生产环境都能高效运行。
### 具体实现步骤
#### 1. 创建加载动画组件 (src/components/Loading.vue)
```vue
<template>
<teleport to="body">
<div v-show="isLoading" class="loading-overlay">
<div class="loading-spinner">
<!-- SVG旋转动画 -->
<svg viewBox="0 0 50 50" class="spinner">
<circle cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
</svg>
<p class="loading-text">{{ text }}</p>
</div>
</div>
</teleport>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
export default defineComponent({
name: 'Loading',
props: {
text: {
type: String,
default: '加载中...'
}
},
setup() {
const isLoading = ref(false);
const show = () => isLoading.value = true;
const hide = () => isLoading.value = false;
// 暴露方法给父组件调用
return { isLoading, show, hide };
}
});
</script>
<style scoped>
.loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.9);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
backdrop-filter: blur(2px); /* 毛玻璃效果 */
}
.loading-spinner {
text-align: center;
}
.spinner {
width: 50px;
height: 50px;
animation: rotate 1.5s linear infinite;
will-change: transform; /* 优化性能 */
}
.spinner circle {
stroke: #3eaf7c; /* 使用Vite的绿色主题色 */
stroke-linecap: round;
stroke-dasharray: 90, 150;
stroke-dashoffset: -35;
animation: dash 1.5s ease-in-out infinite;
}
@keyframes rotate {
100% { transform: rotate(360deg); }
}
@keyframes dash {
0% { stroke-dasharray: 1, 150; stroke-dashoffset: 0; }
50% { stroke-dasharray: 90, 150; stroke-dashoffset: -35; }
100% { stroke-dasharray: 90, 150; stroke-dashoffset: -124; }
}
.loading-text {
margin-top: 15px;
color: #333;
font-size: 16px;
font-weight: 500;
}
</style>
```
#### 2. 在App.vue中使用加载动画组件
```vue
<template>
<div id="app">
<!-- 加载动画组件 -->
<Loading ref="loading" text="正在加载..." />
<!-- 主内容 -->
<router-view v-show="appLoaded" />
</div>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted } from 'vue';
import Loading from '@/components/Loading.vue';
export default defineComponent({
components: { Loading },
setup() {
const loading = ref<InstanceType<typeof Loading> | null>(null);
const appLoaded = ref(false);
// 模拟应用初始化过程
const initApp = async () => {
// 显示加载动画
loading.value?.show();
// 模拟初始化任务(如加载用户信息、获取配置等)
await new Promise(resolve => setTimeout(resolve, 2000));
// 隐藏加载动画,显示主内容
loading.value?.hide();
appLoaded.value = true;
};
onMounted(() => {
initApp();
});
return { loading, appLoaded };
}
});
</script>
```
#### 3. 优化:使用Vite的异步加载特性
在Vite项目中,我们可以结合动态导入优化资源加载:
```typescript
// 在路由配置中使用动态导入
const routes = [
{
path: '/',
component: () => import('@/views/Home.vue'),
meta: { title: '首页' }
}
];
```
### 关键优化点(参考引用内容)
1. **REM/VW布局适配**(引用[3]):
- 在项目中配置`postcss-pxtorem`实现自动单位转换
- 在`vite.config.ts`中配置:
```ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import postcsspxtorem from 'postcss-pxtorem';
export default defineConfig({
plugins: [vue()],
css: {
postcss: {
plugins: [
postcsspxtorem({
rootValue: 37.5, // 设计稿宽度/10
propList: ['*']
})
]
}
}
});
```
2. **性能优化**(引用[1]):
- 利用Vite的按需编译特性,减少初始加载资源
- 使用`will-change: transform`提升动画性能
- 采用`backdrop-filter`实现毛玻璃效果(注意浏览器兼容性)
3. **移动端适配**(引用[2][3]):
```html
<!-- index.html -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
```
### 完整实现流程图
```mermaid
graph TD
A[应用启动] --> B[显示加载动画]
B --> C{执行初始化任务}
C -->|加载资源| D[异步请求/数据初始化]
D --> E{是否完成}
E -->|是| F[隐藏加载动画]
E -->|否| D
F --> G[显示主内容]
```
### 注意事项
1. **SSR兼容**:如果使用SSR,需要确保加载动画在客户端激活
2. **错误处理**:在初始化过程中添加错误处理逻辑
```ts
const initApp = async () => {
try {
loading.value?.show();
await Promise.all([fetchUserInfo(), loadConfig()]);
} catch (error) {
console.error('初始化失败', error);
} finally {
loading.value?.hide();
appLoaded.value = true;
}
};
```
3. **动画性能**:在移动端使用CSS动画而非JavaScript动画以保证流畅性
> 参考Vite-Vue3-H5模板(引用[2][3])的实现方式,我们采用了REM布局方案并集成了Vant组件库,确保在移动端的兼容性和性能[^2][^3]。
§§相关问题§§
1. 如何测量Vue3应用的首次内容绘制(FCP)时间?
2. 在Vite项目中如何实现加载动画的按需引入?
3. 如何为Vue3加载动画添加进度条功能?
4. 在移动端H5中,如何优化加载动画在不同设备上的显示效果?
5. 如何结合Pinia管理全局加载状态?</think>下面我将为您详细介绍在 Vue3 + TypeScript + Vite 的 H5 项目中实现页面初始化加载动画的完整方案,结合引用内容中的最佳实践和技术要点。
### 实现方案
#### 1. 创建加载动画组件 (src/components/Loading.vue)
```vue
<template>
<teleport to="body">
<transition name="fade">
<div
v-show="visible"
class="loading-overlay"
:class="{ 'dark-mode': darkMode }"
>
<div class="spinner-container">
<!-- 动态SVG旋转动画 -->
<svg class="spinner" viewBox="0 0 50 50">
<circle
cx="25"
cy="25"
r="20"
fill="none"
:stroke="color"
stroke-width="5"
></circle>
</svg>
<!-- 进度文本 -->
<div class="progress-text" v-if="showProgress">
{{ progress }}%
</div>
<!-- 提示文本 -->
<div class="hint-text" v-if="hint">
{{ hint }}
</div>
</div>
</div>
</transition>
</teleport>
</template>
<script lang="ts">
import { defineComponent, ref, computed, watch } from 'vue';
export default defineComponent({
name: 'LoadingAnimation',
props: {
// 控制显示/隐藏
visible: {
type: Boolean,
default: false
},
// 加载进度 (0-100)
progress: {
type: Number,
default: 0
},
// 主题颜色
color: {
type: String,
default: '#3eaf7c' // Vite 主题绿
},
// 提示文本
hint: {
type: String,
default: ''
},
// 暗黑模式
darkMode: {
type: Boolean,
default: false
}
},
setup(props) {
// 计算是否显示进度
const showProgress = computed(() => props.progress > 0 && props.progress < 100);
// 暴露控制方法
return { showProgress };
}
});
</script>
<style scoped>
/* 覆盖层样式 */
.loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.92);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
backdrop-filter: blur(3px);
transition: background 0.3s ease;
}
/* 暗黑模式 */
.dark-mode {
background: rgba(0, 0, 0, 0.88);
}
.spinner-container {
text-align: center;
}
/* 旋转动画 */
.spinner {
width: 60px;
height: 60px;
animation: rotate 1.5s linear infinite;
will-change: transform; /* 优化性能 */
}
.spinner circle {
stroke-linecap: round;
stroke-dasharray: 90, 150;
stroke-dashoffset: -35;
animation: dash 1.5s ease-in-out infinite;
}
/* 进度文本 */
.progress-text {
margin-top: 15px;
font-size: 18px;
font-weight: bold;
color: v-bind('color');
transition: color 0.3s;
}
/* 提示文本 */
.hint-text {
margin-top: 8px;
font-size: 14px;
color: #666;
}
.dark-mode .hint-text {
color: #aaa;
}
/* 动画关键帧 */
@keyframes rotate {
100% { transform: rotate(360deg); }
}
@keyframes dash {
0% { stroke-dasharray: 1, 150; stroke-dashoffset: 0; }
50% { stroke-dasharray: 90, 150; stroke-dashoffset: -35; }
100% { stroke-dasharray: 90, 150; stroke-dashoffset: -124; }
}
/* 淡入淡出效果 */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
```
#### 2. 在 App.vue 中集成加载动画 (src/App.vue)
```vue
<template>
<div id="app">
<!-- 全局加载动画 -->
<LoadingAnimation
ref="loader"
:visible="isLoading"
:progress="loadingProgress"
:hint="loadingHint"
:dark-mode="isDarkMode"
/>
<!-- 主内容区域 -->
<router-view v-show="!isLoading" />
</div>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import LoadingAnimation from '@/components/Loading.vue';
// 模拟资源加载
const loadResources = () => {
return new Promise<void>((resolve) => {
setTimeout(() => resolve(), 1500);
});
};
export default defineComponent({
components: { LoadingAnimation },
setup() {
const loader = ref<InstanceType<typeof LoadingAnimation> | null>(null);
const isLoading = ref(true);
const loadingProgress = ref(0);
const loadingHint = ref('正在加载资源...');
const isDarkMode = ref(false);
const router = useRouter();
// 更新加载进度
const updateProgress = (progress: number) => {
loadingProgress.value = Math.min(100, Math.max(0, progress));
// 根据进度更新提示
if (progress < 30) loadingHint.value = '正在加载资源...';
else if (progress < 70) loadingHint.value = '正在初始化应用...';
else loadingHint.value = '即将完成...';
};
// 模拟加载过程
const simulateLoading = async () => {
for (let i = 0; i <= 100; i += 10) {
updateProgress(i);
await new Promise(r => setTimeout(r, 150));
}
await loadResources();
};
// 初始化应用
const initApp = async () => {
try {
await simulateLoading();
isLoading.value = false;
} catch (error) {
console.error('初始化失败', error);
loadingHint.value = '加载失败,请重试';
// 可添加重试逻辑
}
};
// 路由切换时显示加载
router.beforeEach(() => {
if (loader.value) {
isLoading.value = true;
loadingProgress.value = 0;
}
});
router.afterEach(() => {
if (loader.value) {
isLoading.value = false;
}
});
onMounted(initApp);
return {
loader,
isLoading,
loadingProgress,
loadingHint,
isDarkMode
};
}
});
</script>
```
#### 3. 添加 Vite 配置优化 (vite.config.ts)
```typescript
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { createHtmlPlugin } from 'vite-plugin-html';
export default defineConfig({
plugins: [
vue(),
createHtmlPlugin({
minify: true,
inject: {
data: {
loadingStyle: `
<style>
#app-loading {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #f5f5f5;
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
transition: opacity 0.3s;
}
.app-loading-spinner {
width: 40px;
height: 40px;
border: 3px solid rgba(62, 175, 124, 0.3);
border-radius: 50%;
border-top-color: #3eaf7c;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
`
}
}
})
],
build: {
rollupOptions: {
output: {
// 优化块拆分
manualChunks(id) {
if (id.includes('node_modules')) {
return 'vendor';
}
}
}
}
}
});
```
#### 4. 在 index.html 中添加初始加载样式
```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Vite Vue3 H5</title>
<!-- 初始加载样式 -->
<%= loadingStyle %>
</head>
<body>
<div id="app-loading">
<div class="app-loading-spinner"></div>
</div>
<div id="app"></div>
<script type="module">
// 隐藏初始加载动画
window.addEventListener('load', () => {
const loadingEl = document.getElementById('app-loading');
if (loadingEl) {
loadingEl.style.opacity = '0';
setTimeout(() => {
loadingEl.style.display = 'none';
}, 300);
}
});
</script>
</body>
</html>
```
### 关键技术解析
1. **双阶段加载动画**:
- **HTML/CSS 初始加载**:在 Vue 应用启动前显示的简单动画(防止白屏)
- **Vue 组件动画**:应用初始化过程中显示的高级动画(带进度提示)
2. **性能优化**:
- 使用 `will-change: transform` 提升动画性能[^1]
- 配置 Vite 的 `manualChunks` 优化资源加载[^1]
- 使用 `backdrop-filter: blur()` 实现毛玻璃效果
3. **移动端适配**:
- 响应式设计:SVG 矢量图形确保高清显示
- REM/VW 布局支持(参考引用[2][3])
```css
/* 在全局样式中添加 */
html {
font-size: calc(100vw / 7.5); /* 基于750px设计稿 */
}
```
4. **主题定制**:
- 通过 props 传递颜色主题
- 支持暗黑模式切换
- 使用 CSS 变量动态更新样式
5. **加载状态管理**:
- 进度模拟(0-100%)
- 动态提示文本
- 路由切换自动触发
### 最佳实践建议
1. **资源预加载**:
```typescript
// 在加载动画期间预加载关键资源
const preloadImages = (images: string[]) => {
images.forEach(src => {
new Image().src = src;
});
};
```
2. **性能监控**:
```typescript
// 使用 Performance API 监控加载时间
const perfObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
console.log('资源加载时间:', entries);
});
perfObserver.observe({ type: 'resource', buffered: true });
```
3. **骨架屏集成**(进阶):
```vue
<!-- 在加载动画后添加骨架屏 -->
<transition name="fade">
<SkeletonScreen v-if="isLoadingContent" />
</transition>
```
4. **错误处理**:
```typescript
// 添加加载失败处理
window.addEventListener('error', () => {
loadingHint.value = '资源加载失败,请检查网络';
// 显示重试按钮
});
```
5. **动画性能优化**:
```css
.spinner {
transform: translateZ(0); /* 触发GPU加速 */
}
```
### 实现效果
1. **加载流程**:
- 初始 HTML/CSS 加载动画(立即显示)
- Vue 应用启动
- 显示 Vue 加载动画组件
- 资源加载完成
- 隐藏加载动画,显示内容
2. **用户体验优化**:
- 平滑的进度反馈
- 上下文相关的提示信息
- 优雅的过渡动画
- 主题一致性
> 参考 Vite-Vue3-H5 模板(引用[2][3])的实现方式,我们采用了 Vant 组件库的响应式设计原则,确保在移动端的兼容性和性能[^2][^3]。同时结合 Vite 的快速启动特性(引用[1]),优化了加载动画的执行效率[^1]。