mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
116 lines
4.3 KiB
Markdown
116 lines
4.3 KiB
Markdown
# Issue #157435: Touch Scroll Not Propagating to Host Page (Embedded Mode)
|
|
|
|
## Problem Description
|
|
|
|
When a Flutter web app is embedded in a host page using multi-view/custom element embedding (not iframe), touch scrolling inside the Flutter view does not propagate to the host page when the Flutter content reaches its scroll boundary.
|
|
|
|
### User Experience Impact
|
|
- On mobile devices, users cannot scroll the host page by swiping on the Flutter embedded view
|
|
- Touch scrolling feels "stuck" at Flutter content boundaries
|
|
- Poor mobile UX for embedded Flutter content
|
|
|
|
### Difference from Issue #156985
|
|
- Issue #156985 is about **mouse wheel** scrolling in **iframe** embedding
|
|
- Issue #157435 is about **touch** scrolling in **custom element** embedding
|
|
- Both require scroll propagation to parent, but through different mechanisms
|
|
|
|
## Root Cause Analysis
|
|
|
|
1. **`touch-action: none`**: Flutter sets `touch-action: none` on the host element to ensure Flutter receives all touch events for gesture recognition.
|
|
|
|
2. **`preventDefault()` on pointerdown**: The engine was calling `preventDefault()` on ALL pointerdown events, which prevents the browser from initiating native touch scrolling.
|
|
|
|
3. **No touch scroll propagation mechanism**: Unlike wheel events, there was no way for Flutter to propagate touch scroll deltas to the host page when at boundary.
|
|
|
|
## Solution
|
|
|
|
### Engine Changes (`pointer_binding.dart`)
|
|
|
|
**Don't `preventDefault()` for touch events on pointerdown**:
|
|
|
|
```dart
|
|
// In pointerdown handler
|
|
if (event.pointerType != 'touch') {
|
|
event.preventDefault();
|
|
}
|
|
// Touch events: let browser handle scroll initiation
|
|
```
|
|
|
|
This allows the browser's touch scrolling to work, while Flutter still handles touch events first via `touch-action: none`.
|
|
|
|
### Framework Changes (`scroll_position_with_single_context.dart`)
|
|
|
|
Added touch scroll propagation via platform channel:
|
|
|
|
```dart
|
|
static const BasicMessageChannel<Object?> _scrollChannel =
|
|
BasicMessageChannel<Object?>('flutter/scroll', JSONMessageCodec());
|
|
|
|
@override
|
|
void applyUserOffset(double delta) {
|
|
// Check if at boundary before applying scroll
|
|
final bool wasAtMin = pixels <= minScrollExtent;
|
|
final bool wasAtMax = pixels >= maxScrollExtent;
|
|
|
|
// Apply the scroll
|
|
setPixels(pixels - physics.applyPhysicsToUserOffset(this, delta));
|
|
|
|
// On web, propagate to parent when at boundary
|
|
if (kIsWeb) {
|
|
final bool shouldPropagateDown = delta < 0 && (wasAtMax || pixels >= maxScrollExtent);
|
|
final bool shouldPropagateUp = delta > 0 && (wasAtMin || pixels <= minScrollExtent);
|
|
|
|
if (shouldPropagateDown || shouldPropagateUp) {
|
|
_propagateOverscrollToParent(delta);
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Engine Changes (`platform_dispatcher.dart`)
|
|
|
|
Added handler for `flutter/scroll` platform channel:
|
|
|
|
```dart
|
|
case 'flutter/scroll':
|
|
final dynamic decoded = messageCodec.decodeMessage(data);
|
|
if (decoded is Map) {
|
|
final double deltaX = (decoded['deltaX'] as num?)?.toDouble() ?? 0.0;
|
|
final double deltaY = (decoded['deltaY'] as num?)?.toDouble() ?? 0.0;
|
|
scrollParentWindow(deltaX, deltaY);
|
|
replyToPlatformMessage(callback, messageCodec.encodeMessage(true));
|
|
}
|
|
return;
|
|
```
|
|
|
|
### CSS Changes (Embedding Strategies)
|
|
|
|
Added `overscroll-behavior: contain` to prevent any residual scroll chaining:
|
|
|
|
```dart
|
|
// In custom_element_embedding_strategy.dart & full_page_embedding_strategy.dart
|
|
setElementStyle(rootElement, 'overscroll-behavior', 'contain');
|
|
```
|
|
|
|
## Files Changed
|
|
|
|
| File | Change |
|
|
|------|--------|
|
|
| `engine/src/flutter/lib/web_ui/lib/src/engine/pointer_binding.dart` | Don't preventDefault on touch pointerdown |
|
|
| `packages/flutter/lib/src/widgets/scroll_position_with_single_context.dart` | Touch scroll propagation via platform channel |
|
|
| `engine/src/flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart` | `flutter/scroll` channel handler |
|
|
| `engine/src/flutter/lib/web_ui/lib/src/engine/view_embedder/embedding_strategy/*.dart` | `overscroll-behavior: contain` CSS |
|
|
|
|
## Demo
|
|
|
|
- **Before Fix**: https://issue-157435-before.web.app
|
|
- **After Fix**: https://issue-157435-after.web.app
|
|
|
|
## Behavior After Fix
|
|
|
|
1. ✅ Touch scrolling inside Flutter embedded view works normally
|
|
2. ✅ When Flutter content is at boundary, continued swiping scrolls the host page
|
|
3. ✅ Flutter gesture recognition still works (tap, drag, etc.)
|
|
4. ✅ Works for both vertical and horizontal scrolling
|
|
|