<div class="loading-container" id="loading">
<div class="loading-spinner"></div>
<div class="loading-text">Loading...</div>
</div>
<script>
// Hide loading indicator when Flutter app is ready
window.addEventListener('flutter-first-frame', function () {
const loading = document.getElementById('loading');
if (loading) {
loading.style.opacity = '0';
loading.style.transition = 'opacity 0.3s ease-out';
setTimeout(function() {
loading.style.display = 'none';
}, 300);
}
});
// Fallback: hide loading after timeout
setTimeout(function() {
const loading = document.getElementById('loading');
if (loading && loading.style.display !== 'none') {
loading.style.display = 'none';
}
}, 10000);
</script>
这是一个用于 Flutter Web 的自定义加载占位符(Splash Screen),在 Flutter 应用完全加载前显示,主要作用如下:
主要作用
1. 消除白屏/空白页面
-
Flutter Web 应用启动时需要加载 Dart 代码、资源文件等
-
这个期间会有 1-3 秒的空白页面
-
自定义加载界面提供更好的用户体验
2. 显示加载状态
-
告诉用户应用正在加载中
-
减少用户因等待而离开的可能性
3. 平滑过渡
- 当 Flutter 应用准备好后,优雅地淡出加载界面
工作原理
事件监听机制
javascript
// 监听 Flutter 第一帧渲染完成事件
window.addEventListener('flutter-first-frame', function () {
// Flutter 已准备好,可以隐藏加载界面
});
生命周期流程
text
1. 浏览器加载 HTML → 立即显示 loading 动画
2. Flutter 引擎初始化 → 用户看到 loading
3. Flutter 渲染第一帧 → 触发 'flutter-first-frame' 事件
4. 隐藏 loading → 显示 Flutter 应用界面
在 Flutter 项目中的配置
1. 修改 web/index.html
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>My Flutter App</title>
<!-- 加载样式 -->
<style>
.loading-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
z-index: 9999;
transition: opacity 0.5s ease-out;
}
.loading-spinner {
width: 50px;
height: 50px;
border: 5px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: #fff;
animation: spin 1s ease-in-out infinite;
}
.loading-text {
margin-top: 20px;
color: white;
font-size: 18px;
font-family: Arial, sans-serif;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
</head>
<body>
<!-- 自定义加载界面 -->
<div class="loading-container" id="loading">
<div class="loading-spinner"></div>
<div class="loading-text">加载中...</div>
</div>
<!-- Flutter 应用容器 -->
<div id="flutter_target"></div>
<!-- 脚本 -->
<script>
window.addEventListener('flutter-first-frame', function() {
const loading = document.getElementById('loading');
if (loading) {
loading.style.opacity = '0';
setTimeout(() => {
loading.style.display = 'none';
}, 500); // 等待淡出动画完成
}
});
// 超时保护(10秒后强制隐藏)
setTimeout(() => {
const loading = document.getElementById('loading');
if (loading && loading.style.display !== 'none') {
loading.style.display = 'none';
}
}, 10000);
</script>
<!-- Flutter 脚本(由 build 过程自动注入) -->
<script src="flutter.js" defer></script>
</body>
</html>
2. 优化 Flutter 启动速度
dart
void main() {
WidgetsFlutterBinding.ensureInitialized();
// 使用更小的初始应用
runApp(const LoadingApp());
// 后台加载主应用
Future.delayed(Duration.zero, () {
runApp(const MainApp());
});
}
高级用法
1. 根据 Flutter 状态显示不同提示
javascript
<script>
let loadingMessage = document.getElementById('loading-message');
// 监听 Flutter 服务初始化
window.addEventListener('flutter-service-worker', function(event) {
if (loadingMessage) {
loadingMessage.textContent = '正在初始化服务...';
}
});
// 监听资源加载
window.addEventListener('flutter-assets-loading', function(event) {
if (loadingMessage) {
loadingMessage.textContent = `加载资源中... ${event.detail.loaded}/${event.detail.total}`;
}
});
// 第一帧就绪
window.addEventListener('flutter-first-frame', function() {
const loading = document.getElementById('loading');
if (loading) {
loading.style.opacity = '0';
setTimeout(() => {
loading.style.display = 'none';
}, 300);
}
});
</script>
2. 与 Flutter 通信
javascript
<script>
// 从 Flutter 接收消息
window.addEventListener('flutter-initialized', function() {
console.log('Flutter 已初始化');
});
// 向 Flutter 发送消息
function showLoadingMessage(message) {
const loadingText = document.querySelector('.loading-text');
if (loadingText) {
loadingText.textContent = message;
}
}
</script>
dart
// 在 Flutter 中发送消息给 JavaScript
void sendMessageToJS() {
js.context.callMethod('showLoadingMessage', ['Flutter 正在启动...']);
}
性能优化技巧
1. 预加载关键资源
html
<!-- 在 <head> 中添加预加载 -->
<link rel="preload" href="main.dart.js" as="script">
<link rel="preload" href="assets/fonts/custom.woff2" as="font" type="font/woff2" crossorigin>
2. 使用轻量级加载动画
css
/* 使用 CSS 动画而非 GIF,减少资源大小 */
.loading-spinner {
/* 使用简单的 CSS 动画 */
animation: spin 1s linear infinite;
}
3. 渐进式加载
javascript
<script>
// 分阶段显示加载进度
const stages = [
{ event: 'flutter-engine-init', message: '初始化引擎...' },
{ event: 'flutter-assets-loading', message: '加载资源...' },
{ event: 'flutter-widgets-building', message: '构建界面...' },
];
stages.forEach(stage => {
window.addEventListener(stage.event, () => {
updateLoadingMessage(stage.message);
});
});
</script>
常见问题和解决方案
问题1:加载界面闪烁
javascript
// 使用 requestAnimationFrame 确保平滑过渡
window.addEventListener('flutter-first-frame', function() {
requestAnimationFrame(() => {
const loading = document.getElementById('loading');
loading.style.transition = 'opacity 0.5s ease';
loading.style.opacity = '0';
setTimeout(() => {
loading.style.display = 'none';
}, 500);
});
});
问题2:超时处理
javascript
// 更智能的超时处理
let hideTimeout = setTimeout(() => {
const loading = document.getElementById('loading');
if (loading) loading.style.display = 'none';
}, 10000);
// 如果 Flutter 加载成功,清除超时
window.addEventListener('flutter-first-frame', function() {
clearTimeout(hideTimeout);
// ... 隐藏 loading
});
问题3:错误处理
javascript
// 监听 Flutter 加载错误
window.addEventListener('error', function(e) {
if (e.message.includes('flutter') || e.filename.includes('flutter')) {
const loading = document.getElementById('loading');
const loadingText = document.querySelector('.loading-text');
if (loadingText) {
loadingText.textContent = '加载失败,请刷新页面';
loadingText.style.color = '#ff6b6b';
}
// 显示重试按钮
const retryButton = document.createElement('button');
retryButton.textContent = '重试';
retryButton.onclick = () => location.reload();
loading.appendChild(retryButton);
}
});
最佳实践总结
-
立即显示:HTML 加载后立即显示加载界面
-
保持简单:使用纯 CSS 动画,减少资源依赖
-
提供反馈:显示进度或状态信息
-
平滑过渡:使用淡出效果,避免突兀消失
-
错误处理:考虑加载失败的情况
-
超时保护:避免永远显示加载界面
这个方案能显著提升 Flutter Web 应用的用户体验,让用户感觉应用加载更快、更专业。