In a Flutter web app, how do you embed a real HTML element such as an iframe, and what does it cost?
answer
- a platform view for the web
- HtmlElementView.fromTagName with onElementCreated
- cast the element with package:web
- the element swallows pointer events
- each overlay splits the canvas
basics
~20 sHtmlElementView reserves layout space for a DOM element: fromTagName creates it and onElementCreated configures it through package:web. The element receives pointer events Flutter never sees, and every one splits the canvas into overlays that cost memory and speed.
solid answer
~40 sOn the web, `HtmlElementView` is Flutter's platform view: it reserves a box in the layout and places a real DOM element there. `HtmlElementView.fromTagName(tagName: 'iframe', onElementCreated: ...)` creates the element and hands it over as an `Object`, which you cast to a `package:web` type such as `web.HTMLIFrameElement` to set `src` and styles. For more control, register a factory with `platformViewRegistry.registerViewFactory` from `dart:ui_web` and use `HtmlElementView(viewType: ...)`. Two costs follow. The element handles its own pointer events, so Flutter widgets drawn over it, such as a dialog or a floating button, can miss taps; `PointerInterceptor` from `pointer_interceptor` fixes that, and an iframe always swallows events inside it. And the engine must split its canvas into **overlays** above and below each element, which costs memory and time; pass `isVisible: false` for elements that paint nothing.
code
dart · 22 linesimport 'package:flutter/widgets.dart';
import 'package:web/web.dart' as web;
class RouteReportFrame extends StatelessWidget {
const RouteReportFrame({super.key, required this.reportUrl});
final String reportUrl;
@override
Widget build(BuildContext context) {
return HtmlElementView.fromTagName(
tagName: 'iframe',
onElementCreated: (Object element) {
final frame = element as web.HTMLIFrameElement;
frame.src = reportUrl;
frame.style.border = 'none';
frame.style.width = '100%';
frame.style.height = '100%';
},
);
}
}go deeper
Recall that HtmlElementView embeds a real DOM element in a Flutter web layout, created with fromTagName or a registered view factory.
Explain onElementCreated with package:web types, why overlaid Flutter widgets miss pointer events and how PointerInterceptor helps.
Show you manage overlay count, isVisible and event routing when third-party web content lives inside a Flutter web app.
Judge when embedding web content is a reasonable seam and when the embedded part should be rebuilt in Flutter or kept as a separate page.
## Why a Flutter web app needs a special widget A Flutter web app draws everything into a canvas. Sometimes that is not enough: a third-party map SDK, a `<video>` element, a payment form or an existing HTML report must stay real web content. Flutter's answer is a **platform view**, and on the web the widget is **`HtmlElementView`**. It takes part in layout like any widget, receives constraints and a size, and the engine positions a real DOM element over that rectangle. Native Android and iOS platform views are a different mechanism with their own widgets; this is the web-only one. ## Two ways to create the element | Approach | When | |---|---| | `HtmlElementView.fromTagName(tagName: ..., onElementCreated: ...)` | you only need an element by tag name and some setup | | `platformViewRegistry.registerViewFactory(viewType, factory)` from `dart:ui_web`, then `HtmlElementView(viewType: ...)` | you build the element yourself, reuse a factory across views, or pass `creationParams` | With `fromTagName`, `onElementCreated` receives the element as an `Object`. It is a JavaScript object, and you work with it through **`package:web`**, casting to the matching type such as `web.HTMLIFrameElement` or `web.HTMLVideoElement`. `package:web` and `dart:js_interop` replace the old `dart:html`, which also keeps the code compatible with Wasm builds. ## Cost one: pointer events The DOM element is interactive, so it receives browser pointer events directly. That has two consequences: - **Flutter content on top can miss taps.** If a dialog, a drawer or a floating button is drawn over the element, clicks may land in the element first. The `pointer_interceptor` package's `PointerInterceptor` widget places an empty, invisible platform view behind its child so the browser routes the event to Flutter. - **Iframes are opaque to Flutter's gestures.** Clicks, drags and drops inside an `<iframe>` never reach Flutter's gesture system; that is the browser's isolation, not a bug. - Setting CSS `pointer-events: none` on the element in `onElementCreated` gives all events to Flutter, but then the element itself cannot be used. ## Cost two: overlays To show HTML content between canvas pixels, the engine splits its drawing into parts painted behind the element and parts painted above it. Each split is an **overlay**. Overlays cost memory and rendering time, and a browser supports only a limited number per scene; the CanvasKit setting `canvasKitMaximumSurfaces` caps how many it uses. 1. Keep the number of simultaneously visible `HtmlElementView`s small. 2. Pass `isVisible: false` for elements that paint nothing, such as the interceptor or an invisible link target, so the engine does not waste an overlay. 3. Avoid placing a platform view inside each row of a long scrolling list. ## Checklist before embedding - Is there a Flutter widget or package that does the job natively? Rebuilding a small control in Flutter avoids overlays and event routing entirely. - Does the content need to be interactive? If not, `pointer-events: none` hands all input to Flutter. - Which Flutter widgets will be drawn over it? Each one needs `PointerInterceptor` or a layout that keeps it clear. - Does it paint pixels? If not, pass `isVisible: false`. - Does it load cross-origin content? Under COEP headers for Wasm threads, the embedded page must allow embedding. ## A worked case A delivery service's Flutter admin dashboard can embed an existing HTML route report in an `<iframe>` inside a card, while the rest of the dashboard stays Flutter. The team wraps the dashboard's floating action button and its filter drawer in `PointerInterceptor`, because both are drawn over the report, and keeps one report visible at a time to limit overlays.
- A Flutter web dialog opened over an HtmlElementView ignores clicks on its buttons. What is happening, and what is the fix?The DOM element under the dialog receives the browser's pointer events before Flutter sees them. Wrap the dialog content in `PointerInterceptor` from the `pointer_interceptor` package, which puts an invisible platform view behind it so the browser delivers the click to Flutter.
- When would you pass isVisible: false to an HtmlElementView in Flutter web?When the element paints no pixels, as with the invisible element behind `PointerInterceptor` or a link target. `isVisible` tells the engine whether it must split the canvas into an overlay around the element; `false` saves that overlay. Leave it `true`, the default, for anything that draws.
An HtmlElementView is like cutting a window into a painted stage backdrop and putting a real object behind it: the audience sees the object in place, but anything painted on top has to be a separate panel, and every extra window means another panel to handle.
saying these in an interview costs you the question
- HtmlElementView gives you a Flutter widget that looks like the HTML element.
- Flutter's GestureDetector receives taps that land inside an iframe.
- Any number of HtmlElementViews can be shown with no rendering cost.
- You should configure the element through dart:html in current Flutter.
- AndroidView and HtmlElementView are interchangeable across platforms.