【Flutter web】ボタンがクリックできない?pointer_interceptorで解決

【Flutter web】ボタンがクリックできない?pointer_interceptorで解決

Flutter webでモーダル上のボタンなどがクリックできない謎

既存のFlutterアプリをweb化もして動作確認していたところ、Youtube動画のiframeを表示している画面でモーダルを表示してモーダルの「はい」「いいえ」のボタンをクリックしてみました。

なぜかYoutube動画の再生停止ボタンをクリックしたことになっていて、モーダル上のボタンがクリックできていません。

KeyboardDismissOnTap(
	dismissOnCapturedTaps: false,
	child: Dialog(),
),

初めは、KeyboardDismissOnTapが原因かと思いましたが、削除しても治らない…

Flutter webでボタンなどがクリックできない原因

Flutter webでボタンなどがクリックできない原因

In the dashed areas, mouse events won't work as expected. The HtmlElementView will consume them before Flutter sees them. (破線の部分では、マウスイベントは期待通りに動作しません。Flutterがそれを見る前にHtmlElementViewがそれを消費してしまう。)

なるほど。。。

マウスジェスチャーに反応するHtmlElementViewPlatformViewウィジェットの上にFlutterウィジェットを重ねると、クリックはHtmlElementViewPlatformViewによって消費され、Flutterには中継されないみたいです。

その結果、FlutterウィジェットのonTapハンドラは期待通りに発火しないんですね。

HtmlElementView上でもonTapを実行するためにpointer_interceptorパッケージで解決

PointerInterceptorを使うことで、Web 上の基盤によってマウスイベントがキャプチャされるのを防ぐウィジェットです。

HtmlElementView上でもonTapを実行するためにpointer_interceptorパッケージで解決

PointerInterceptor(緑)はFlutterウィジェットとその下のHtmlElementViewの間でレンダリングします。

マウスイベントが背景のHtmlElementViewに届かないようになり、期待通りに動くようになりました。

kIsWeb
  ? PointerInterceptor(
      child: Dialog(),
    )
  : KeyboardDismissOnTap(
      dismissOnCapturedTaps: false,
      child: Dialog(),
    ),

注意点:多用するとパフォーマンスが悪化する

Using multiple PointerInterceptor instances on iOS can be slow and increases memory usage due to the performance overhead of the underlying platform view.

iOSで複数のPointerInterceptorインスタンスを使用すると、基盤となるプラットフォーム ビューのパフォーマンス オーバーヘッドにより、速度が低下し、メモリ使用量が増加する可能性があります。

まとめ

Flutter Webでは、HtmlElementViewPlatformView(今回のケースだとYouTubeの <iframe>)が背面にあると、その領域がタップやクリックイベントを奪ってしまい、上に重ねたFlutterウィジェットのボタンが反応しないという落とし穴がありました。

Web特有の挙動なので、モバイルアプリ開発だけだと気づきにくいですが、実際に遭遇してみると「なぜボタンが効かないのか?」という謎が理解できました。

今後、Flutterを Webに展開する際は、HtmlElementViewのクリック競合問題とPointerInterceptorの活用 を覚えておくと安心です。

記事を書いた人

Matsuura

エンジニア

Matsuura

Anycloudでエンジニアしてます!主にFlutter・Typescript