Flutter Web 混合开发:构建跨平台 Web 应用
什么是 Flutter Web?
Flutter Web 是 Flutter 框架的 Web 支持,它允许开发者使用 Flutter 的 UI 框架和 Dart 语言来构建 Web 应用。Flutter Web 将 Dart 代码编译为 JavaScript,使其能够在浏览器中运行。
Flutter Web 的优势
- 单一代码库:一套代码可以同时构建 Web、移动端和桌面端应用。
Flutter Web 混合开发技术,阐述了其作为 Flutter 框架 Web 支持的核心概念及优势,包括单一代码库、高性能渲染、丰富 UI 组件等。内容涵盖环境搭建步骤、Web 特定配置(index.html 与 manifest.json)、关键功能实现(响应式布局、浏览器导航、JS 交互、PWA 支持)以及性能优化方案(代码分割、图片优化、减少重绘)。此外,文章提供了完整的博客案例实战代码,并详细说明了部署至 Firebase、GitHub Pages 及 Netlify 的具体流程,旨在帮助开发者构建美观高效的跨平台 Web 应用。
Flutter Web 是 Flutter 框架的 Web 支持,它允许开发者使用 Flutter 的 UI 框架和 Dart 语言来构建 Web 应用。Flutter Web 将 Dart 代码编译为 JavaScript,使其能够在浏览器中运行。
# 启用 Web 支持
flutter config --enable-web
# 验证 Web 支持
flutter devices
# 创建新项目
flutter create my_web_app
# 进入项目目录
cd my_web_app
# 运行 Web 应用
flutter run -d chrome
# 构建 Web 应用(发布模式)
flutter build web
# 构建 Web 应用(调试模式)
flutter build web --debug
# 构建 Web 应用(指定基础路径)
flutter build web --base-href "/my-app/"
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="My Flutter Web App">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="My App">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>My Flutter Web App</title>
<link rel="manifest" href="manifest.json">
<!-- 添加自定义样式 -->
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
}
/* 加载指示器 */
.loading {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-family: Arial, sans-serif;
font-size: 18px;
color: #667eea;
}
</style>
</head>
<body>
<!-- 加载指示器 -->
<div id="loading">加载中...</div>
<script src="flutter.js" defer></script>
<script>
window.addEventListener('load', function(ev) {
// 下载 main.dart.js
_flutter.loader.loadEntrypoint({
serviceWorker: {
serviceWorkerVersion: serviceWorkerVersion,
},
onEntrypointLoaded: function(engineInitializer) {
engineInitializer.initializeEngine().then(function(appRunner) {
appRunner.runApp();
// 隐藏加载指示器
document.getElementById('loading').style.display = 'none';
});
}
});
});
</script>
</body>
</html>
{
"name": "My Flutter Web App",
"short_name": "My App",
"start_url": ".",
"display": "standalone",
"background_color": "#667eea",
"theme_color": "#667eea",
"description": "A Flutter Web application",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512"
}
]
}
import 'package:flutter/material.dart';
class ResponsiveLayout extends StatelessWidget {
const ResponsiveLayout({super.key});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < 600) {
return const MobileLayout();
} else if (constraints.maxWidth < 1200) {
return const TabletLayout();
} else {
return const DesktopLayout();
}
},
);
}
}
class MobileLayout extends StatelessWidget {
const MobileLayout({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('移动端布局')),
body: const Center(child: Text('这是移动端布局')),
);
}
}
class TabletLayout extends StatelessWidget {
const TabletLayout({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('平板布局')),
body: const Center(child: Text('这是平板布局')),
);
}
}
class DesktopLayout extends StatelessWidget {
const DesktopLayout({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('桌面端布局')),
body: const Center(child: Text('这是桌面端布局')),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
void main() {
// 使用路径 URL 策略(移除 URL 中的 #)
setUrlStrategy(PathUrlStrategy());
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Web',
theme: ThemeData(primarySwatch: Colors.blue),
initialRoute: '/',
routes: {
'/': (context) => const HomePage(),
'/about': (context) => const AboutPage(),
'/contact': (context) => const ContactPage(),
},
);
}
}
import 'dart:js' as js;
import 'dart:html' as html;
class JsInteropExample {
// 调用 JavaScript 函数
static void callJsFunction() {
js.context.callMethod('alert', ['Hello from Flutter!']);
}
// 获取 JavaScript 变量
static dynamic getJsVariable(String name) {
return js.context[name];
}
// 设置 JavaScript 变量
static void setJsVariable(String name, dynamic value) {
js.context[name] = value;
}
// 调用 JavaScript 对象的方法
static void callJsObjectMethod() {
final obj = js.JsObject.jsify({
'name': 'Flutter',
'greet': (name) => 'Hello, $name!',
});
final result = obj.callMethod('greet', ['World']);
print(result); // Hello, World!
}
// 使用 HTML5 API
static void useHtml5Api() {
// 获取当前 URL
final url = html.window.location.href;
print('Current URL: $url');
// 跳转到新页面
html.window.location.href = 'https://flutter.dev';
// 获取本地存储
final storage = html.window.localStorage;
storage['key'] = 'value';
final value = storage['key'];
// 获取浏览器信息
final userAgent = html.window.navigator.userAgent;
print('User Agent: $userAgent');
}
}
import 'dart:html' as html;
class PwaService {
// 检查是否支持 Service Worker
static bool get isServiceWorkerSupported {
return html.window.navigator.serviceWorker != null;
}
// 注册 Service Worker
static Future<void> registerServiceWorker() async {
if (isServiceWorkerSupported) {
try {
final registration = await html.window.navigator.serviceWorker!
.register('flutter_service_worker.js');
print('Service Worker registered: ${registration.scope}');
} catch (e) {
print('Service Worker registration failed: $e');
}
}
}
// 检查是否已安装 PWA
static bool get isPwaInstalled {
return html.window.matchMedia('(display-mode: standalone)').matches;
}
// 请求安装 PWA
static void requestPwaInstall() {
// 需要在 beforeinstallprompt 事件中保存事件
// 然后在这里触发
}
}
// 使用 deferred 关键字延迟加载库
import 'package:my_app/heavy_feature.dart' deferred as heavy_feature;
class LazyLoadedPage extends StatefulWidget {
const LazyLoadedPage({super.key});
@override
State<LazyLoadedPage> createState() => _LazyLoadedPageState();
}
class _LazyLoadedPageState extends State<LazyLoadedPage> {
bool _isLoaded = false;
@override
void initState() {
super.initState();
_loadLibrary();
}
Future<void> _loadLibrary() async {
await heavy_feature.loadLibrary();
setState(() {
_isLoaded = true;
});
}
@override
Widget build(BuildContext context) {
if (!_isLoaded) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
return Scaffold(
body: heavy_feature.HeavyFeatureWidget(),
);
}
}
import 'package:flutter/material.dart';
class OptimizedImage extends StatelessWidget {
final String imageUrl;
final double width;
final double height;
const OptimizedImage({
super.key,
required this.imageUrl,
required this.width,
required this.height,
});
@override
Widget build(BuildContext context) {
return Image.network(
imageUrl,
width: width,
height: height,
fit: BoxFit.cover,
// 使用缓存
cacheWidth: width.toInt() * 2,
cacheHeight: height.toInt() * 2,
// 加载占位符
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Container(
width: width,
height: height,
color: Colors.grey[300],
child: const Center(child: CircularProgressIndicator()),
);
},
// 错误处理
errorBuilder: (context, error, stackTrace) {
return Container(
width: width,
height: height,
color: Colors.grey[300],
child: const Icon(Icons.error),
);
},
);
}
}
import 'package:flutter/material.dart';
class OptimizedWidget extends StatelessWidget {
const OptimizedWidget({super.key});
@override
Widget build(BuildContext context) {
return const RepaintBoundary(
child: ComplexWidget(),
);
}
}
class ComplexWidget extends StatelessWidget {
const ComplexWidget({super.key});
@override
Widget build(BuildContext context) {
return Container(
// 复杂的内容
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
void main() {
setUrlStrategy(PathUrlStrategy());
runApp(const BlogApp());
}
class BlogApp extends StatelessWidget {
const BlogApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Web 博客',
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
initialRoute: '/',
routes: {
'/': (context) => const HomePage(),
'/post': (context) => const PostPage(),
'/about': (context) => const AboutPage(),
},
);
}
}
// 首页
class HomePage extends StatelessWidget {
const HomePage({super.key});
final List<Map<String, String>> posts = const [
{
'title': 'Flutter Web 入门指南',
'excerpt': '学习如何使用 Flutter 构建 Web 应用...',
'date': '2024-03-31',
},
{
'title': '响应式布局最佳实践',
'excerpt': '掌握 Flutter 中的响应式设计技巧...',
'date': '2024-03-30',
},
{
'title': '性能优化技巧',
'excerpt': '提升 Flutter Web 应用性能的实用方法...',
'date': '2024-03-29',
},
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter Web 博客'),
actions: [
TextButton(
onPressed: () => Navigator.pushNamed(context, '/about'),
child: const Text('关于', style: TextStyle(color: Colors.white)),
),
],
),
body: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: Center(
child: Container(
constraints: const BoxConstraints(maxWidth: 800),
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'最新文章',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 24),
...posts.map((post) => PostCard(post: post)),
],
),
),
),
);
},
),
);
}
}
// 文章卡片
class PostCard extends StatelessWidget {
final Map<String, String> post;
const PostCard({super.key, required this.post});
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.only(bottom: 16),
child: InkWell(
onTap: () => Navigator.pushNamed(context, '/post'),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
post['title']!,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
post['excerpt']!,
style: TextStyle(
fontSize: 16,
color: Colors.grey[600],
),
),
const SizedBox(height: 12),
Text(
post['date']!,
style: TextStyle(
fontSize: 14,
color: Colors.grey[500],
),
),
],
),
),
),
);
}
}
// 文章详情页
class PostPage extends StatelessWidget {
const PostPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('文章详情'),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context),
),
),
body: SingleChildScrollView(
child: Center(
child: Container(
constraints: const BoxConstraints(maxWidth: 800),
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Flutter Web 入门指南',
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
Text(
'2024-03-31',
style: TextStyle(
fontSize: 14,
color: Colors.grey[500],
),
),
const SizedBox(height: 24),
const Text(
'Flutter Web 是 Flutter 框架的 Web 支持,它允许开发者使用 Flutter 的 UI 框架和 Dart 语言来构建 Web 应用。',
style: TextStyle(
fontSize: 18,
height: 1.6,
),
),
const SizedBox(height: 16),
const Text(
'在本文中,我们将学习如何搭建 Flutter Web 开发环境,创建第一个 Web 应用,以及了解 Web 开发的最佳实践。',
style: TextStyle(
fontSize: 18,
height: 1.6,
),
),
],
),
),
),
),
);
}
}
// 关于页面
class AboutPage extends StatelessWidget {
const AboutPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('关于'),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context),
),
),
body: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Flutter Web 博客',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 16),
Text(
'使用 Flutter Web 构建的现代化博客应用',
style: TextStyle(
fontSize: 16,
color: Colors.grey,
),
),
],
),
),
);
}
}
flutter build web --release
# 安装 Firebase CLI
npm install -g firebase-tools
# 登录 Firebase
firebase login
# 初始化项目
firebase init hosting
# 部署
firebase deploy
# 构建 Web 应用
flutter build web --release
# 复制到 docs 目录
cp -r build/web docs
# 提交并推送
git add docs
git commit -m "Deploy to GitHub Pages"
git push
# 构建 Web 应用
flutter build web --release
# 部署到 Netlify
netlify deploy --prod --dir=build/web
Flutter Web 为开发者提供了一种强大的方式来构建跨平台 Web 应用。通过掌握 Flutter Web 的开发技巧和最佳实践,我们可以创建出既美观又高性能的 Web 应用。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online