mirror of
https://github.com/flutter/flutter
synced 2024-08-24 18:36:03 +00:00
[web][docs] Improve HtmlElementView widget docs. (#145192)
This PR expands the `HtmlElementView` widget DartDocs, with the following sections: * Usage: How to use the widget, two ways: * The `HtmlElementView.fromTagName` constructor * The `PlatformViewRegistry` way * Lifecycle: There's an `onCreated` callback on the widget. When does it get called? * HTML Lifecycle: How to listen to events coming from the DOM. * Visibility: what is the `isVisible` property for? Small additional tweaks here and there to mention common pitfalls of using HtmlElementView on the web, and mentions to workarounds, like `package:pointer_interceptor`. ## Issues * Fixes: https://github.com/flutter/flutter/issues/143922 * Fixes: https://github.com/flutter/flutter/issues/49634 * Related: https://github.com/flutter/website/issues/5063
This commit is contained in:
parent
6836b0445a
commit
df75d249b2
|
@ -19,6 +19,8 @@ import 'framework.dart';
|
||||||
// PlatformViewController createFooWebView(PlatformViewCreationParams params) { return (null as dynamic) as PlatformViewController; }
|
// PlatformViewController createFooWebView(PlatformViewCreationParams params) { return (null as dynamic) as PlatformViewController; }
|
||||||
// Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers = <Factory<OneSequenceGestureRecognizer>>{};
|
// Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers = <Factory<OneSequenceGestureRecognizer>>{};
|
||||||
// late PlatformViewController _controller;
|
// late PlatformViewController _controller;
|
||||||
|
// void myOnElementCreated(Object element) {}
|
||||||
|
// void myOnPlatformViewCreated(int viewId) {}
|
||||||
|
|
||||||
/// Embeds an Android view in the Widget hierarchy.
|
/// Embeds an Android view in the Widget hierarchy.
|
||||||
///
|
///
|
||||||
|
@ -358,49 +360,291 @@ class AppKitView extends _DarwinView {
|
||||||
State<AppKitView> createState() => _AppKitViewState();
|
State<AppKitView> createState() => _AppKitViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Callback signature for when the platform view's DOM element was created.
|
/// The signature of the function that gets called when the [HtmlElementView]
|
||||||
|
/// DOM element is created.
|
||||||
///
|
///
|
||||||
/// [element] is the DOM element that was created.
|
/// [element] is the DOM element that was created.
|
||||||
///
|
///
|
||||||
/// Also see [HtmlElementView.fromTagName] that uses this callback
|
/// This callback is called before [element] is attached to the DOM, so it can
|
||||||
/// signature.
|
/// be modified as needed by the Flutter web application.
|
||||||
|
///
|
||||||
|
/// See [HtmlElementView.fromTagName] that receives a callback of this type.
|
||||||
|
///
|
||||||
|
/// {@template flutter.widgets.web.JSInterop.object}
|
||||||
|
/// Flutter uses type `Object` so this API doesn't force any JS interop API
|
||||||
|
/// implementation to Flutter users. This `element` can be cast to any compatible
|
||||||
|
/// JS interop type as needed. For example: `JSAny` (from `dart:js_interop`),
|
||||||
|
/// `HTMLElement` (from `package:web`) or any custom JS interop definition.
|
||||||
|
/// See "Next-generation JS interop": https://dart.dev/interop/js-interop
|
||||||
|
/// {@endtemplate}
|
||||||
typedef ElementCreatedCallback = void Function(Object element);
|
typedef ElementCreatedCallback = void Function(Object element);
|
||||||
|
|
||||||
/// Embeds an HTML element in the Widget hierarchy in Flutter Web.
|
/// Embeds an HTML element in the Widget hierarchy in Flutter web.
|
||||||
///
|
///
|
||||||
/// *NOTE*: This only works in Flutter Web. To embed web content on other
|
/// The embedded HTML is laid out like any other Flutter widget and
|
||||||
/// platforms, consider using the `flutter_webview` plugin.
|
/// transformations (like opacity, and clipping) apply to it as well.
|
||||||
///
|
|
||||||
/// Embedding HTML is an expensive operation and should be avoided when a
|
|
||||||
/// Flutter equivalent is possible.
|
|
||||||
///
|
|
||||||
/// The embedded HTML is painted just like any other Flutter widget and
|
|
||||||
/// transformations apply to it as well. This widget should only be used in
|
|
||||||
/// Flutter Web.
|
|
||||||
///
|
///
|
||||||
/// {@macro flutter.widgets.AndroidView.layout}
|
/// {@macro flutter.widgets.AndroidView.layout}
|
||||||
///
|
///
|
||||||
/// Due to security restrictions with cross-origin `<iframe>` elements, Flutter
|
/// Embedding HTML is a _potentially expensive_ operation and should be avoided
|
||||||
/// cannot dispatch pointer events to an HTML view. If an `<iframe>` is the
|
/// when a Flutter equivalent is possible. (See **`isVisible` parameter** below.)
|
||||||
/// target of an event, the window containing the `<iframe>` is not notified
|
/// This widget is useful to integrate native HTML elements to a Flutter web app,
|
||||||
/// of the event. In particular, this means that any pointer events which land
|
/// like a `<video>` tag, or a `<div>` where a [Google Map](https://pub.dev/packages/google_maps_flutter)
|
||||||
/// on an `<iframe>` will not be seen by Flutter, and so the HTML view cannot
|
/// can be rendered.
|
||||||
/// participate in gesture detection with other widgets.
|
|
||||||
///
|
///
|
||||||
/// The way we enable accessibility on Flutter for web is to have a full-page
|
/// This widget **only works on Flutter web.** To embed web content on other
|
||||||
/// button which waits for a double tap. Placing this full-page button in front
|
/// platforms, consider using the [`webview_flutter` plugin](https://pub.dev/packages/webview_flutter).
|
||||||
/// of the scene would cause platform views not to receive pointer events. The
|
|
||||||
/// tradeoff is that by placing the scene in front of the semantics placeholder
|
|
||||||
/// will cause platform views to block pointer events from reaching the
|
|
||||||
/// placeholder. This means that in order to enable accessibility, you must
|
|
||||||
/// double tap the app *outside of a platform view*. As a consequence, a
|
|
||||||
/// full-screen platform view will make it impossible to enable accessibility.
|
|
||||||
/// Make sure that your HTML views are sized no larger than necessary, or you
|
|
||||||
/// may cause difficulty for users trying to enable accessibility.
|
|
||||||
///
|
///
|
||||||
/// {@macro flutter.widgets.AndroidView.lifetime}
|
/// ## Usage
|
||||||
|
///
|
||||||
|
/// There's two ways to use the `HtmlElementView` widget:
|
||||||
|
///
|
||||||
|
/// ### `HtmlElementView.fromTagName`
|
||||||
|
///
|
||||||
|
/// The [HtmlElementView.fromTagName] constructor creates the HTML element
|
||||||
|
/// specified by `tagName`, and passes it to the `onElementCreated` callback
|
||||||
|
/// where it can be customized:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// // In a `build` method...
|
||||||
|
/// HtmlElementView.fromTagName(
|
||||||
|
/// tagName: 'div',
|
||||||
|
/// onElementCreated: myOnElementCreated,
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The example creates a `<div>` element, then calls the `onElementCreated`
|
||||||
|
/// callback with the created `<div>`, so it can be customized **before** it is
|
||||||
|
/// attached to the DOM.
|
||||||
|
///
|
||||||
|
/// (See more details about `onElementCreated` in the **Lifecycle** section below.)
|
||||||
|
///
|
||||||
|
/// ### Using the `PlatformViewRegistry`
|
||||||
|
///
|
||||||
|
/// The primitives used to implement [HtmlElementView.fromTagName] are available
|
||||||
|
/// for general use through `dart:ui_web`'s `platformViewRegistry`.
|
||||||
|
///
|
||||||
|
/// Creating an `HtmlElementView` through these primitives is a two step process:
|
||||||
|
///
|
||||||
|
/// #### 1. `registerViewFactory`
|
||||||
|
///
|
||||||
|
/// First, a `viewFactory` function needs to be registered for a given `viewType`.
|
||||||
|
/// Flutter web will call this factory function to create the `element` that will
|
||||||
|
/// be attached later:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// import 'dart:ui_web' as ui_web;
|
||||||
|
/// import 'package:web/web.dart' as web;
|
||||||
|
///
|
||||||
|
/// void registerRedDivFactory() {
|
||||||
|
/// ui_web.platformViewRegistry.registerViewFactory(
|
||||||
|
/// 'my-view-type',
|
||||||
|
/// (int viewId, {Object? params}) {
|
||||||
|
/// // Create and return an HTML Element from here
|
||||||
|
/// final web.HTMLDivElement myDiv = web.HTMLDivElement()
|
||||||
|
/// ..id = 'some_id_$viewId'
|
||||||
|
/// ..style.backgroundColor = 'red'
|
||||||
|
/// ..style.width = '100%'
|
||||||
|
/// ..style.height = '100%';
|
||||||
|
/// return myDiv;
|
||||||
|
/// },
|
||||||
|
/// );
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// `registerViewFactory` **must** be called outside of `build` methods, so the
|
||||||
|
/// registered function is available when `build` happens.
|
||||||
|
///
|
||||||
|
/// See the different types of functions that can be used as `viewFactory`:
|
||||||
|
///
|
||||||
|
/// * [`typedef ui_web.PlatformViewFactory`](https://api.flutter.dev/flutter/dart-ui_web/PlatformViewFactory.html)
|
||||||
|
/// * [`typedef ui_web.ParameterizedPlatformViewFactory`](https://api.flutter.dev/flutter/dart-ui_web/ParameterizedPlatformViewFactory.html)
|
||||||
|
///
|
||||||
|
/// #### 2. `HtmlElementView` widget
|
||||||
|
///
|
||||||
|
/// Once a factory is registered, an `HtmlElementView` widget of `viewType` can
|
||||||
|
/// be added to the widget tree, like so:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// // In a `build` method...
|
||||||
|
/// const HtmlElementView(
|
||||||
|
/// viewType: 'my-view-type',
|
||||||
|
/// onPlatformViewCreated: myOnPlatformViewCreated,
|
||||||
|
/// creationParams: <String, Object?>{
|
||||||
|
/// 'key': 'someValue',
|
||||||
|
/// },
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [viewType] **must** match the value used to `registerViewFactory` before.
|
||||||
|
///
|
||||||
|
/// [creationParams] (optional) will be passed to your `viewFactory` function,
|
||||||
|
/// if it accepts them.
|
||||||
|
///
|
||||||
|
/// [onPlatformViewCreated] will be called with the `viewId` of the platform
|
||||||
|
/// view (`element`) created by the `viewFactory`, before it gets attached to
|
||||||
|
/// the DOM.
|
||||||
|
///
|
||||||
|
/// The `viewId` can be used to retrieve the created `element` (The same one
|
||||||
|
/// passed to `onElementCreated` in [HtmlElementView.fromTagName]) with the
|
||||||
|
/// `ui_web.platformViewRegistry.`[`getViewById` method](https://api.flutter.dev/flutter/dart-ui_web/PlatformViewRegistry/getViewById.html).
|
||||||
|
///
|
||||||
|
/// (See more details about `onPlatformViewCreated` in the **Lifecycle** section
|
||||||
|
/// below.)
|
||||||
|
///
|
||||||
|
/// ## Lifecycle
|
||||||
|
///
|
||||||
|
/// `HtmlElementView` widgets behave like any other Flutter stateless widget, but
|
||||||
|
/// with an additional lifecycle method: `onPlatformViewCreated` / `onElementCreated`
|
||||||
|
/// (depending on the constructor, see **Usage** above).
|
||||||
|
///
|
||||||
|
/// The difference between the two callbacks is the parameter they receive:
|
||||||
|
///
|
||||||
|
/// * `onPlatformViewCreated` will be called with the created `viewId` as a parameter,
|
||||||
|
/// and needs `ui_web.platformViewRegistry.getViewById` to retrieve the created
|
||||||
|
/// element (See [PlatformViewCreatedCallback]).
|
||||||
|
/// * `onElementCreated` will be called with the created `element` directly,
|
||||||
|
/// skipping its `viewId` (See [ElementCreatedCallback]).
|
||||||
|
///
|
||||||
|
/// Both callbacks are called **after** the HTML `element` has been created, but
|
||||||
|
/// **before** it's attached to the DOM.
|
||||||
|
///
|
||||||
|
/// ### HTML Lifecycle
|
||||||
|
///
|
||||||
|
/// The Browser DOM APIs have additional HTML lifecycle callbacks for the root
|
||||||
|
/// `element` of an `HtmlElementView`.
|
||||||
|
///
|
||||||
|
/// #### Element Attached To The DOM
|
||||||
|
///
|
||||||
|
/// It is common for JS code to locate the DOM elements they need with a
|
||||||
|
/// selector, rather than accepting said DOM elements directly. In those cases,
|
||||||
|
/// the `element` **must** be attached to the DOM for the selector to work.
|
||||||
|
///
|
||||||
|
/// The example below demonstrates **how to create an `onElementAttached` function**
|
||||||
|
/// that gets called when the root `element` is attached to the DOM using a
|
||||||
|
/// `ResizeObserver` through `package:web` from the `onElementCreated` lifecycle
|
||||||
|
/// method:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// import 'dart:js_interop';
|
||||||
|
/// import 'package:web/web.dart' as web;
|
||||||
|
///
|
||||||
|
/// // Called after `element` is attached to the DOM.
|
||||||
|
/// void onElementAttached(web.HTMLDivElement element) {
|
||||||
|
/// final web.Element? located = web.document.querySelector('#someIdThatICanFindLater');
|
||||||
|
/// assert(located == element, 'Wrong `element` located!');
|
||||||
|
/// // Do things with `element` or `located`, or call your code now...
|
||||||
|
/// element.style.backgroundColor = 'green';
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// void onElementCreated(Object element) {
|
||||||
|
/// element as web.HTMLDivElement;
|
||||||
|
/// element.style.backgroundColor = 'red';
|
||||||
|
/// element.id = 'someIdThatICanFindLater';
|
||||||
|
///
|
||||||
|
/// // Create the observer
|
||||||
|
/// final web.ResizeObserver observer = web.ResizeObserver((
|
||||||
|
/// JSArray<web.ResizeObserverEntry> entries,
|
||||||
|
/// web.ResizeObserver observer,
|
||||||
|
/// ) {
|
||||||
|
/// if (element.isConnected) {
|
||||||
|
/// // The observer is done, disconnect it.
|
||||||
|
/// observer.disconnect();
|
||||||
|
/// // Call our callback.
|
||||||
|
/// onElementAttached(element);
|
||||||
|
/// }
|
||||||
|
/// }.toJS);
|
||||||
|
///
|
||||||
|
/// // Connect the observer.
|
||||||
|
/// observer.observe(element);
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// * Read more about [`ResizeObserver` in the MDN](https://developer.mozilla.org/en-US/docs/Web/API/Resize_Observer_API).
|
||||||
|
///
|
||||||
|
/// #### Other Observers
|
||||||
|
///
|
||||||
|
/// The example above uses a `ResizeObserver` because it can be applied directly
|
||||||
|
/// to the `element` that is about to be attached. Another observer that could
|
||||||
|
/// be used for this (with a little bit more code) would be a
|
||||||
|
/// [`MutationObserver`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver).
|
||||||
|
///
|
||||||
|
/// The `MutationObserver` requires the parent element in which the `HtmlElementView`
|
||||||
|
/// is going to be inserted. A safe way to retrieve a parent element for the
|
||||||
|
/// platform view is to retrieve the `hostElement` of the [FlutterView] where the
|
||||||
|
/// `HtmlElementView` is being rendered.
|
||||||
|
///
|
||||||
|
/// The `hostElement` of the current [FlutterView] can be retrieved through:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// import 'dart:js_interop';
|
||||||
|
/// import 'dart:ui_web' as ui_web;
|
||||||
|
/// import 'package:flutter/widgets.dart';
|
||||||
|
///
|
||||||
|
/// void useHostElement(BuildContext context) {
|
||||||
|
/// final int flutterViewId = View.of(context).viewId;
|
||||||
|
/// final JSAny? hostElement = ui_web.views.getHostElement(flutterViewId);
|
||||||
|
/// // Use `package:web` with `hostElement`...
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// **Important:** `FlutterView.viewId` and the `viewId` parameter passed to
|
||||||
|
/// the `viewFactory` identify **different objects**:
|
||||||
|
///
|
||||||
|
/// * `flutterViewId` (from `View.of(context)`) represents the [FlutterView]
|
||||||
|
/// where the web app is currently rendering.
|
||||||
|
/// * `viewId` (passed to the `viewFactory` function) represents a unique ID
|
||||||
|
/// for the `HtmlElementView` instance that is being attached to the app.
|
||||||
|
///
|
||||||
|
/// Read more about [FlutterView] on Flutter's API docs:
|
||||||
|
///
|
||||||
|
/// * [`View.of`](https://api.flutter.dev/flutter/widgets/View/of.html)
|
||||||
|
/// * [`getHostElement`](https://main-api.flutter.dev/flutter/dart-ui_web/FlutterViewManagerProxy/getHostElement.html)
|
||||||
|
///
|
||||||
|
/// ## Pointer events
|
||||||
|
///
|
||||||
|
/// In order for the `HtmlElementView` contents to be interactive, they're allowed
|
||||||
|
/// to handle `pointer-events`. This may result in Flutter missing some events
|
||||||
|
/// because they've been handled by the `HtmlElementView`, and not seen by
|
||||||
|
/// Flutter.
|
||||||
|
///
|
||||||
|
/// [`package:pointer_interceptor`](https://pub.dev/packages/pointer_interceptor)
|
||||||
|
/// may help in some cases where Flutter content needs to be overlaid on top of
|
||||||
|
/// an `HtmlElementView`. Alternatively, the `pointer-events: none` property can
|
||||||
|
/// be set `onElementCreated`; but that will prevent **ALL** interactions with
|
||||||
|
/// the underlying HTML content.
|
||||||
|
///
|
||||||
|
/// If the `HtmlElementView` is an `<iframe>` element, Flutter will not receive
|
||||||
|
/// pointer events that land in the `<iframe>` (click/tap, drag, drop, etc.)
|
||||||
|
/// In those cases, the `HtmlElementView` will seem like it's _swallowing_
|
||||||
|
/// the events and not participating in Flutter's gesture detection.
|
||||||
|
///
|
||||||
|
/// ## `isVisible` parameter
|
||||||
|
///
|
||||||
|
/// Rendering custom HTML content (from `HtmlElementView`) in between `canvas`
|
||||||
|
/// pixels means that the Flutter web engine needs to _split_ the canvas drawing
|
||||||
|
/// into elements drawn _behind_ the HTML content, and those drawn _above_ it.
|
||||||
|
///
|
||||||
|
/// In the Flutter web engine, each of these _splits of the canvas to sandwich
|
||||||
|
/// HTML content in between_ is referred to as an **overlay**.
|
||||||
|
///
|
||||||
|
/// Each _overlay_ present in a scene has implications both in memory and
|
||||||
|
/// execution performance, and it is best to minimize their amount; browsers
|
||||||
|
/// support a limited number of _overlays_ on a single scene at a given time.
|
||||||
|
///
|
||||||
|
/// `HtmlElementView` objects have an `isVisible` property that can be passed
|
||||||
|
/// through `registerViewFactory`, or `fromTagName`. `isVisible` refers
|
||||||
|
/// to whether the `HtmlElementView` will paint pixels on the screen or not.
|
||||||
|
///
|
||||||
|
/// Correctly defining this value helps the Flutter web rendering engine optimize
|
||||||
|
/// the amount of _overlays_ it'll need to render a particular scene.
|
||||||
|
///
|
||||||
|
/// In general, `isVisible` should be left to its default value of `true`, but
|
||||||
|
/// in some `HtmlElementView`s (like the `pointer_interceptor` or `Link` widget),
|
||||||
|
/// it can be set to `false`, so the engine doesn't _waste_ an overlay to render
|
||||||
|
/// Flutter content on top of views that don't paint any pixels.
|
||||||
class HtmlElementView extends StatelessWidget {
|
class HtmlElementView extends StatelessWidget {
|
||||||
/// Creates a platform view for Flutter Web.
|
/// Creates a platform view for Flutter web.
|
||||||
///
|
///
|
||||||
/// `viewType` identifies the type of platform view to create.
|
/// `viewType` identifies the type of platform view to create.
|
||||||
const HtmlElementView({
|
const HtmlElementView({
|
||||||
|
@ -419,6 +663,7 @@ class HtmlElementView extends StatelessWidget {
|
||||||
///
|
///
|
||||||
/// [onElementCreated] is called when the DOM element is created. It can be
|
/// [onElementCreated] is called when the DOM element is created. It can be
|
||||||
/// used by the app to customize the element by adding attributes and styles.
|
/// used by the app to customize the element by adding attributes and styles.
|
||||||
|
/// This method is called *before* the element is attached to the DOM.
|
||||||
factory HtmlElementView.fromTagName({
|
factory HtmlElementView.fromTagName({
|
||||||
Key? key,
|
Key? key,
|
||||||
required String tagName,
|
required String tagName,
|
||||||
|
@ -439,6 +684,8 @@ class HtmlElementView extends StatelessWidget {
|
||||||
|
|
||||||
/// Callback to invoke after the platform view has been created.
|
/// Callback to invoke after the platform view has been created.
|
||||||
///
|
///
|
||||||
|
/// This method is called *before* the platform view is attached to the DOM.
|
||||||
|
///
|
||||||
/// May be null.
|
/// May be null.
|
||||||
final PlatformViewCreatedCallback? onPlatformViewCreated;
|
final PlatformViewCreatedCallback? onPlatformViewCreated;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue