Flutter Web性能优化标签解析(二)

复制代码
<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);
  }
});

最佳实践总结

  1. 立即显示:HTML 加载后立即显示加载界面

  2. 保持简单:使用纯 CSS 动画,减少资源依赖

  3. 提供反馈:显示进度或状态信息

  4. 平滑过渡:使用淡出效果,避免突兀消失

  5. 错误处理:考虑加载失败的情况

  6. 超时保护:避免永远显示加载界面

这个方案能显著提升 Flutter Web 应用的用户体验,让用户感觉应用加载更快、更专业。

相关推荐
FreeBuf_14 小时前
RondoDox僵尸网络利用高危React2Shell漏洞劫持IoT设备与Web服务器
前端·网络·物联网
阿蔹14 小时前
UI测试自动化-Web-Python-Appium
前端·python·ui·appium·自动化
Lethehong14 小时前
TextIn 赋能!Dify+DeepSeek 高效搭建新能源汽车销量可视化工作流
大数据·前端·python·textin·蓝耘元生代·蓝耘maas
亿元程序员14 小时前
爆款拆解与实现:动态画出物理线条,手把手教你制作“画线救狗”
前端
weixin_5316518114 小时前
File.stream() 与 FormData 技术详解
开发语言·前端·javascript
走在路上的菜鸟14 小时前
Android学Flutter学习笔记 第三节 Android视角认知Flutter(触摸事件,List,Text,Input)
android·学习·flutter
董世昌4114 小时前
如何声明一个类?类如何继承?
java·开发语言·前端
2501_9419820514 小时前
企业微信 API 外部群主动推送技术解析
前端·chrome