Lets the mobile browser address bar collapse in full-page Flutter web apps on Android and iOS, while scrolling stays in Flutter's own scroll views.
Try it on a phone:
- JavaScript: https://koji-1009.github.io/web_address_bar/
- WebAssembly: https://koji-1009.github.io/web_address_bar/wasm/
A mobile browser collapses its address bar when the user scrolls the document. A full-page Flutter web app gives it nothing to scroll: the page is exactly as tall as the viewport, and <body> has touch-action: none, so every touch goes to Flutter.
This package gives the browser a document of its own to scroll, under a Flutter view that stays fixed, and rebuilds the touch input the browser then cancels.
- Flutter 3.50.0 or later.
- A full-page app with the implicit view (no custom
hostElement, no multi-view), with<flutter-view>under<body>. - A page that does not already scroll itself.
enabledoes nothing if<body>hastouch-actionother thannone,overflow-yis changed on<html>or<body>, or the document is taller than the viewport.
It takes effect in browsers on Android and iOS (iPadOS included), compiled to JavaScript or WebAssembly. It does nothing in other browsers and on platforms other than the web, so an app for several platforms can call it as is.
import 'package:web_address_bar/web_address_bar.dart';
void main() {
WebAddressBar.enable();
runApp(const MyApp());
}enablecan be called before or afterrunApp, but not while a finger is on the screen, such as fromonTapDown: that touch would stay down.platformslimitsenableto Android or iOS, for exampleWebAddressBar.enable(platforms: {TargetPlatform.iOS}).WebAddressBar.isActivetells whetherenabletook effect. In debug builds, the reason it did not (see Requirements) is printed.WebAddressBar.disable()undoes everything.WebAddressBar.collapsedExtentis how much of the viewport the browser's UI covers, in logical pixels: the bar's height while it shows, 0 once it has collapsed.
Safari answers a tap on the iOS status bar by scrolling the document, which the package has taken over. Add WebAddressBarScrollToTop above the Navigator to scroll the primary scroll view in the foreground to the top instead, as a native iOS app does:
MaterialApp(
builder: (context, child) => WebAddressBarScrollToTop(child: child!),
// ...
);Only a scroll view in the foreground is scrolled: a page under another route or a dialog, and hidden tabs, such as those an IndexedStack or an Offstage does not show, are left alone.
- Touch input carries no pressure or tilt. A pen on the screen arrives as a touch; a hovering pen is reported as a stylus as usual. The movement of a pen, or of more than one finger, follows the browser's throttled
touchmove. Apps that need pressure or tilt should not enable the package. - Pull-to-refresh of the page is unavailable by design.
- With semantics enabled, the engine gives its scrollable semantics nodes
touch-action: none, so that the framework handles their scrolling (semantics/scrollable.dart). A drag that starts on one does not collapse the bar. - The browser does not report a status bar tap. It is inferred from a scroll toward the top with no finger on the screen.
<body> gets touch-action: pan-y, so the browser reads a vertical drag as a scroll, and overflow-y: auto, so the document can scroll. <body> stays position: fixed, so the app does not move.
A 10000px spacer under <html> gives the document a scroll range, and scroll-snap-type: y mandatory keeps it parked in the middle, out of reach of pull-to-refresh and rubber-banding.
- Android: two snap points, at 5000px and 5001px. Chrome scrolls some way before it hides its bar and shows it again on a scroll back up, so a scroll rests on the snap point in the direction it went.
- iOS: one snap point, at 5000px.
Under pan-y the browser cancels the touch Pointer Events mid-drag, so the touch input is rebuilt from Touch Events and pointerrawupdate and handed to the engine as touch Pointer Events, which it handles as usual. Platform views and text fields still get the browser's own events first.
Engines without flutter/flutter#194109 can remove the accessibility placeholder in the middle of a drag that started on it. The package follows a contact on the element it started on, so the drag still ends.
Based on flutter/flutter#184281.
example/ has test screens for lists, nested scrollables, interactive widgets, a page that does not scroll, sliver app bars, tabs, pull to refresh, pinch, a platform view, turning the package off and on, collapsedExtent and the long-drag repro of flutter/flutter#194104, with what to check on a device.
MIT.