将 Flutter 添加到任意 Web 应用
了解将 Flutter 视图嵌入 Web 内容的不同方式。
Flutter 视图与 Web 内容可以不同方式组合成 Web 应用。请根据用例选择以下之一:
-
Flutter 视图控制整页(full page mode,全页模式)
-
将 Flutter 视图添加到现有 Web 应用(嵌入模式)
全页模式
#在全页模式下,Flutter Web 应用控制整个浏览器窗口,渲染时完全覆盖其视口。
这是新 Flutter Web 项目的默认嵌入模式,无需额外配置。
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<script src="flutter_bootstrap.js" defer></script>
</body>
</html>
当 Flutter Web 启动时未引用 multiViewEnabled 或 hostElement 时,将使用全页模式。
要了解 flutter_bootstrap.js 文件的更多信息,请参阅 自定义应用初始化。
iframe 嵌入
#
通过 iframe 嵌入 Flutter Web 应用时,建议使用全页模式。嵌入 iframe 的页面可按需设置其大小与位置,Flutter 将完全填充它。
<iframe src="https://url-to-your-flutter/index.html"></iframe>
要了解 iframe 的优缺点,请参阅 MDN 上的 Inline Frame element
文档。
嵌入模式
#
Flutter Web 应用也可将内容渲染到另一 Web 应用的任意数量元素(通常是 div)中;这称为「嵌入模式」(或「多视图」)。
在此模式下:
-
Flutter Web 应用可以启动,但在通过
addView添加第一个「视图」之前不会渲染。 -
宿主应用可以向嵌入的 Flutter Web 应用添加或移除视图。
-
添加或移除视图时 Flutter 应用会收到通知,从而相应调整其 widget。
启用多视图模式
#
在 initializeEngine 方法中设置 multiViewEnabled: true 以启用多视图模式,如下所示:
{{flutter_js}}
{{flutter_build_config}}
_flutter.loader.load({
onEntrypointLoaded: async function onEntrypointLoaded(engineInitializer) {
let engine = await engineInitializer.initializeEngine({
multiViewEnabled: true, // Enables embedded mode.
});
let app = await engine.runApp();
// Make this `app` object available to your JS app.
}
});
从 JS 管理 Flutter 视图
#要添加或移除视图,请使用 runApp 方法返回的 app 对象:
// Adding a view...
let viewId = app.addView({
hostElement: document.querySelector('#some-element'),
});
// Removing viewId...
let viewConfig = app.removeView(viewId);
从 Dart 处理视图变更
#
视图的添加与移除通过 WidgetsBinding 类的 didChangeMetrics method
暴露给 Flutter。
附加到 Flutter 应用的完整视图列表可通过 WidgetsBinding.instance.platformDispatcher.views 迭代器获取。这些视图的 类型为 FlutterView。
要在每个 FlutterView 中渲染内容,Flutter 应用需要创建 View widget。
View widget 可在 ViewCollection widget
下分组。
以下示例来自 Multi View Playground,将上述逻辑封装在可用作应用根 widget 的 MultiViewApp widget 中。每个 FlutterView 都会运行 WidgetBuilder function:
import 'dart:ui' show FlutterView;
import 'package:flutter/widgets.dart';
/// Calls [viewBuilder] for every view added to the app to obtain the widget to
/// render into that view. The current view can be looked up with [View.of].
class MultiViewApp extends StatefulWidget {
const MultiViewApp({super.key, required this.viewBuilder});
final WidgetBuilder viewBuilder;
@override
State<MultiViewApp> createState() => _MultiViewAppState();
}
class _MultiViewAppState extends State<MultiViewApp> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_updateViews();
}
@override
void didUpdateWidget(MultiViewApp oldWidget) {
super.didUpdateWidget(oldWidget);
// Need to re-evaluate the viewBuilder callback for all views.
_views.clear();
_updateViews();
}
@override
void didChangeMetrics() {
_updateViews();
}
Map<Object, Widget> _views = <Object, Widget>{};
void _updateViews() {
final Map<Object, Widget> newViews = <Object, Widget>{};
for (final FlutterView view in WidgetsBinding.instance.platformDispatcher.views) {
final Widget viewWidget = _views[view.viewId] ?? _createViewWidget(view);
newViews[view.viewId] = viewWidget;
}
setState(() {
_views = newViews;
});
}
Widget _createViewWidget(FlutterView view) {
return View(
view: view,
child: Builder(
builder: widget.viewBuilder,
),
);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
Widget build(BuildContext context) {
return ViewCollection(views: _views.values.toList(growable: false));
}
}
更多信息请参阅 API 文档中的 WidgetsBinding mixin,或开发时使用的
Multi View Playground repo。
在 Dart 中用 runWidget 替换 runApp
#
Flutter 的 runApp function
假定至少有一个可用于渲染的视图 (implicitView),但在 Flutter Web 多视图模式下 implicitView 不再存在,因此 runApp 将开始因 Unexpected null value 错误而失败。
在多视图模式下,main.dart 必须改为调用 runWidget function。它不需要 implicitView,只会渲染到已显式添加到应用中的视图。
以下示例使用上文 MultiViewApp,在每个可用的 FlutterView 上渲染 MyApp() widget 的副本:
void main() {
runWidget(
MultiViewApp(
viewBuilder: (BuildContext context) => const MyApp(),
),
);
}
识别视图
#
每个 FlutterView 在附加时由 Flutter 分配标识符。该 viewId 可用于唯一标识每个视图、获取其初始配置或决定渲染内容。
已渲染 FlutterView 的 viewId 可通过其 BuildContext 如下获取:
class SomeWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Retrieve the `viewId` where this Widget is being built:
final int viewId = View.of(context).viewId;
// ...
同样,在 MultiViewApp 的 viewBuilder 方法中,可如下获取 viewId:
MultiViewApp(
viewBuilder: (BuildContext context) {
// Retrieve the `viewId` where this Widget is being built:
final int viewId = View.of(context).viewId;
// Decide what to render based on `viewId`...
},
)
了解更多请参阅 View.of constructor。
初始视图配置
#
Flutter 视图在启动时可从 JS 接收任意初始化数据。值通过 addView 方法的 initialData 属性传递,如下所示:
// Adding a view with initial data...
let viewId = app.addView({
hostElement: someElement,
initialData: {
greeting: 'Hello, world!',
randomValue: Math.floor(Math.random() * 100),
}
});
在 Dart 中,initialData 作为 JSAny 对象可用,可通过 dart:ui_web 库中的顶层 views 属性访问。数据通过当前视图的 viewId 访问,如下所示:
final initialData = ui_web.views.getInitialData(viewId) as YourJsInteropType;
要了解如何定义 YourJsInteropType 类以映射从 JS 传入的 initialData 对象,从而在 Dart 程序中实现类型安全,请参阅 dart.dev 上的 JS Interoperability。
视图约束
#
默认情况下,嵌入的 Flutter Web 视图将 hostElement 的大小视为不可变属性,并严格将布局约束在可用空间内。
在 Web 上,元素的固有尺寸常会影响页面布局(例如 img 或 p 标签可使周围内容重排)。
向 Flutter Web 添加视图时,可配置约束以告知 Flutter 视图应如何布局:
// Adding a view with initial data...
let viewId = app.addView({
hostElement: someElement,
viewConstraints: {
maxWidth: 320,
minHeight: 0,
maxHeight: Infinity,
}
});
从 JS 传入的视图约束需与嵌入 Flutter 的 hostElement 的 CSS 样式兼容。例如,若 CSS 为 max-height: 100px 而向 Flutter 传入 maxHeight: Infinity,
Flutter 不会试图“修复”这类矛盾常量。
了解更多请参阅 ViewConstraints class
与 理解布局约束。
自定义元素(hostElement)
#
你可以将单视图 Flutter Web 应用嵌入网页的任意 HTML 元素。
要指定 Flutter Web 渲染到哪个元素,请向 _flutter.loader.load 传入带 config 字段的对象,并将 HTMLElement 指定为 hostElement。
_flutter.loader.load({
config: {
hostElement: document.getElementById('flutter_host'),
}
});
要了解其他配置选项,请参阅 自定义 Web 应用初始化。
除非另有说明,本文档之所提及适用于 Flutter 3.44.0 版本。本页面最后更新时间:2026-06-15。查看文档源码 或者 为本页面内容提出建议。