跳转至正文

在 Flutter Web 应用中嵌入 Web 内容

了解如何在 Flutter Web 应用中嵌入 Web 内容、理解 DOM 插槽层,以及处理指针事件。

某些情况下,Flutter Web 应用需要嵌入不由 Flutter 渲染的 Web 内容。例如嵌入 google_maps_flutter 视图(使用 Google Maps JavaScript SDK)或 video_player(使用标准 video 元素)。

Flutter Web 可在 widget 边界内渲染任意 Web 内容,前述示例 package 所用的底层能力对所有 Flutter Web 应用均可用。

HtmlElementView

#

HtmlElementView Flutter widget 在布局中预留空间,由任意 HTML 元素填充。它有两个构造函数:

  • HtmlElementView.fromTagName

    HtmlElementView.fromTagName

  • HtmlElementViewregisterViewFactory

HtmlElementView.fromTagName

#

HtmlElementView.fromTagName constructor 根据 tagName 创建 HTML 元素,并提供 onElementCreated 方法,在注入 DOM 之前配置该元素:

dart
// Create a `video` tag, and set its `src` and some `style` properties...
HtmlElementView.fromTagName(
  tagName: 'video',
  onElementCreated: (Object video) {
    video as web.HTMLVideoElement;
    video.src =
        'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4';
    video.style.width = '100%';
    video.style.height = '100%';
    // other customizations to the element...
  },
);

要了解如何与 DOM API 交互,请参阅 package:web 中的 HTMLVideoElement class

要了解为何将 Object 转换为 web.HTMLVideoElement,请参阅 Dart 的 JS Interoperability 文档。

HtmlElementViewregisterViewFactory

#

若需更精细地控制所注入的 HTML,可使用 Flutter 实现 fromTagName 构造函数的底层能力。此时需为应用中每种要添加的 HTML 内容注册自己的 HTML 元素工厂。

生成的代码针对每种平台视图类型包含两个步骤:

  1. 使用 dart:ui_web 提供的 platformViewRegistry.registerViewFactory 注册 HTML 元素工厂。

  2. 在应用 widget 树中放置带有所需 viewTypeHtmlElementView(viewType: 'viewType') widget。

有关该方式的更多细节,请参阅 HtmlElementView widget 文档。

Fixing hit testing issues

#

Because of how Flutter web performs hit testing, underlying platform views might sometimes swallow pointer events before they can reach Flutter.

To prevent losing pointer events intended for Flutter widgets, use the package:pointer_interceptor package.

The PointerInterceptor widget creates an empty, transparent platform view and places it directly behind its child widget in paint order. This transparent element catches browser pointer events before they reach the underlying HtmlElementView, allowing Flutter's gesture framework to handle the interaction as expected.

Add the dependency

#

Add pointer_interceptor to your pubspec.yaml file:

yaml
dependencies:
  pointer_interceptor: ^0.10.1+2

Wrap interactive widgets

#

To make an individual interactive widget clickable over an HtmlElementView (such as a FloatingActionButton or ElevatedButton), wrap the widget with PointerInterceptor:

dart
PointerInterceptor(
  child: FloatingActionButton(
    onPressed: () {
      // Handle button press
    },
    child: const Icon(Icons.add),
  ),
)

Wrap layout and overlay containers

#

When you use overlay components that cover a platform view, such as a Drawer, dialog, or popup menu, wrap the container widget in a PointerInterceptor:

dart
Scaffold(
  drawer: PointerInterceptor(
    child: Drawer(
      child: ListView(
        children: [
          ListTile(
            title: const Text('Home'),
            onTap: () {
              // Handle navigation
            },
          ),
        ],
      ),
    ),
  ),
  body: HtmlElementView.fromTagName(tagName: 'iframe'),
)

Conditionally intercept events

#

The intercepting property lets you enable or disable pointer interception based on a boolean condition:

dart
PointerInterceptor(
  intercepting: isOverlayVisible,
  child: ElevatedButton(
    onPressed: () {
      // Handle button press
    },
    child: const Text('Submit'),
  ),
)

When intercepting is false, PointerInterceptor renders its child directly without creating an extra platform view in the DOM.

Debug interceptor bounds

#

The PointerInterceptor widget includes a debug property. Setting debug: true renders a visible colored overlay over the interceptor area, helping you verify the size and position of the intercepted region:

dart
PointerInterceptor(
  debug: true,
  child: ElevatedButton(
    onPressed: () {},
    child: const Text('Click me'),
  ),
)

package:webview_flutter

#

在 Flutter 应用中嵌入完整 HTML 页面非常常见,Flutter 团队提供了相应插件: