跳转至正文

将 Flutter 添加到任意 Web 应用

了解将 Flutter 视图嵌入 Web 内容的不同方式。

Flutter 视图与 Web 内容可以不同方式组合成 Web 应用。请根据用例选择以下之一:

全页模式

#

在全页模式下,Flutter Web 应用控制整个浏览器窗口,渲染时完全覆盖其视口。

这是新 Flutter Web 项目的默认嵌入模式,无需额外配置。

html
<!DOCTYPE html>
<html>
  <head>
  </head>
  <body>
    <script src="flutter_bootstrap.js" defer></script>
  </body>
</html>

当 Flutter Web 启动时未引用 multiViewEnabledhostElement 时,将使用全页模式。

要了解 flutter_bootstrap.js 文件的更多信息,请参阅 自定义应用初始化

iframe 嵌入

#

通过 iframe 嵌入 Flutter Web 应用时,建议使用全页模式。嵌入 iframe 的页面可按需设置其大小与位置,Flutter 将完全填充它。

html
<iframe src="https://url-to-your-flutter/index.html"></iframe>

要了解 iframe 的优缺点,请参阅 MDN 上的 Inline Frame element 文档。

Compare iframe and direct DOM embedding

#

When integrating Flutter into an existing web application, choose between iframe embedding and direct DOM embedding (hostElement or multi-view) based on your isolation, performance, and communication requirements:

Feature iframe embedding Direct DOM embedding (hostElement / multi-view)
Isolation & sandboxing Full browser sandboxing with separate JS global scope, DOM tree, and CSS styles. Shares the same DOM tree and JavaScript context.
Memory & engine instances Each iframe initializes its own Flutter engine, WebAssembly/JS runtime, and memory heap. A single Flutter engine instance and memory heap can manage one or more views (when using multiview).
JavaScript interop Communication requires asynchronous messaging (such as postMessage). Direct synchronous communication using package:web and dart:js_interop.
Styling & layout Independent frame isolated from host page CSS. Direct integration into host page CSS layout (such as flexbox and grid).
State sharing State must be synchronized across window boundaries. Direct state sharing in Dart across all attached views.
Best for Third-party widgets, untrusted content, isolated micro-frontends, or simple drop-in embeds. Embedded UI components in existing web apps, multi-view dashboards, and tight host-app integration.

嵌入模式

#

Flutter Web 应用也可将内容渲染到另一 Web 应用的任意数量元素(通常是 div)中;这称为「嵌入模式」(或「多视图」)。

在此模式下:

  • Flutter Web 应用可以启动,但在通过 addView 添加第一个「视图」之前不会渲染。

  • 宿主应用可以向嵌入的 Flutter Web 应用添加或移除视图。

  • 添加或移除视图时 Flutter 应用会收到通知,从而相应调整其 widget。

启用多视图模式

#

initializeEngine 方法中设置 multiViewEnabled: true 以启用多视图模式,如下所示:

flutter_bootstrap.js
js
{{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 对象:

js
// 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 widgetView widget 可在 ViewCollection widget 下分组。

以下示例来自 Multi View Playground,将上述逻辑封装在可用作应用根 widget 的 MultiViewApp widget 中。每个 FlutterView 都会运行 WidgetBuilder function

multi_view_app.dart
dart
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

在 Dart 中用 runWidget 替换 runApp

#

Flutter 的 runApp function 假定至少有一个可用于渲染的视图 (implicitView),但在 Flutter Web 多视图模式下 implicitView 不再存在,因此 runApp 将开始因 Unexpected null value 错误而失败。

在多视图模式下,main.dart 必须改为调用 runWidget function。它不需要 implicitView,只会渲染到已显式添加到应用中的视图。

以下示例使用上文 MultiViewApp,在每个可用的 FlutterView 上渲染 MyApp() widget 的副本:

main.dart
dart
void main() {
  runWidget(
    MultiViewApp(
      viewBuilder: (BuildContext context) => const MyApp(),
    ),
  );
}

识别视图

#

每个 FlutterView 在附加时由 Flutter 分配标识符。该 viewId 可用于唯一标识每个视图、获取其初始配置或决定渲染内容。

已渲染 FlutterViewviewId 可通过其 BuildContext 如下获取:

dart
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;
    // ...

同样,在 MultiViewAppviewBuilder 方法中,可如下获取 viewId

dart
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 属性传递,如下所示:

js
// 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 访问,如下所示:

dart
final initialData = ui_web.views.getInitialData(viewId) as YourJsInteropType;

要了解如何定义 YourJsInteropType 类以映射从 JS 传入的 initialData 对象,从而在 Dart 程序中实现类型安全,请参阅 dart.dev 上的 JS Interoperability

视图约束

#

默认情况下,嵌入的 Flutter Web 视图将 hostElement 的大小视为不可变属性,并严格将布局约束在可用空间内。

在 Web 上,元素的固有尺寸常会影响页面布局(例如 imgp 标签可使周围内容重排)。

向 Flutter Web 添加视图时,可配置约束以告知 Flutter 视图应如何布局:

js
// 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

js
_flutter.loader.load({
  config: {
    hostElement: document.getElementById('flutter_host'),
  }
});

要了解其他配置选项,请参阅 自定义 Web 应用初始化

Host element CSS requirements

#

When embedding Flutter directly into a DOM element (using hostElement with single-view or addView in multi-view mode), Flutter sizes its rendering canvas based on the computed dimensions of the host container.

HTML container elements like <div> default to height: auto and position: static. Without explicit CSS styling, an empty host element has a computed height of 0px. This causes Flutter to compute the view height as zero, which collapses the Flutter view and prevents content from rendering.

Mandatory CSS properties

#

To ensure Flutter renders correctly, apply the following CSS properties to your host element:

  • width and height: Set explicit or relative dimensions (for example, width: 100%; height: 500px; or height: 100% within a sized parent). The element must evaluate to a non-zero width and height.
  • position: relative (or position: absolute / position: fixed): Establishes a positioning context for the canvas and DOM overlay layers that Flutter creates inside the container.
  • overflow: hidden: Prevents Flutter canvas and interaction elements from overflowing the container boundaries or causing unexpected scrollbars on the host page.

Example CSS

#
styles.css
css
#flutter_host {
  width: 100%;
  height: 600px;
  position: relative;
  overflow: hidden;
}
index.html
html
<div id="flutter_host"></div>