放流作業

ネットの海に放流するのでいつか戻ってきてほしい

Flutter Webでホームページを作ってみた所感

Flutter Webでホームページを公開しました

こんにちは、Fastriver(@fastriver_org)です。先日私の所属しているKCS(Computer Society)の新歓特設サイトを制作、公開させていただきました。

kcs1959.jp

Flutterはモバイル向けのフレームワークなので、「普通の」Webサイトを作ってみた事例は少ないと思われるためここに所感を書いておきます。

環境

>flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel unknown, v1.14.6, on Microsoft Windows [Version 10.0.18363.720], locale ja-JP)

[√] Android toolchain - develop for Android devices (Android SDK version 29.0.1)
[√] Chrome - develop for the web
[√] Android Studio (version 3.6)
[√] IntelliJ IDEA Community Edition (version 2019.2)
[√] VS Code (version 1.43.1)
[√] Connected device (2 available)

• No issues found!

公開時点(2020/03/21)では Beta channelはv1.15.17なのですが、flutter runがうまく作動しないためv1.14.6に戻して作業しています。

レスポンシブ対応

Webサイトのレイアウトを考える上でレスポンシブ対応は非常に重要です。 特にflutterだとモバイル向けのデザインになりがちですが、それをPCで見るとつらいものがあります。

f:id:Nageler:20200324140144p:plain
AppBarやTabBarはそのまま使うときつい

ですがFlutter、よく出来ているのでレスポンシブに対応するためのLayoutが多く揃っています。 私はページのベースとしてLayoutBuilderを使いました。

LayoutBuilder

api.flutter.dev

LayoutBuilderはbuilderの引数にBoxConstraintsを持ち、現在の"そのWidgetの"大きさを教えてくれます。 またサイズが変わると都度リビルドをかけてくれるので、その値によってLayoutを組み直します。

情報としては画面のアスペクト比・PC or Phoneが欲しかったので以下のクラスを定義してツリーに流しました。

その他

その他レスポンシブに便利だったWidgetたちです。

AspectRatio

api.flutter.dev

画面の比率に関わらずWidgetを一定の縦横比にリサイズしてくれます

Align

api.flutter.dev

基本はStack内で使うものですが、Alignで囲むと大きさが(部分的に)親に依存しなくなるので便利です

Wrap

api.flutter.dev

Column, Rowに似たもので、Mainの幅が足りなくなると自動的に折り返してくれます。chipとかによく使われるやつですね

Expanded

api.flutter.dev

ColumnやRowに使います。余り部分を埋めてくれます

AutoSizeText

pub.dev

幅に合わせて文字の大きさを自動的に決定してくれます。最高

ExpandedGrid

pub.dev

私が作ったもので、こんな感じのViewが作れます。

パフォーマンス

Flutterさん、クロスプラットフォームにしてはAndroid, iOSでの実行速度が速く問題ないだろうと思っていたのですが、 Flutter Webは現状かなり重いです。

それもそのはずそれぞれの実装は

であり、またFlutter Webは殆どをCanvasで描画しているということがあります。 Stable版に期待したいところです。

特に気になる点

起動

main.dart.jsの読み込みに時間がかかります。

Google PageSpeed Insightsでみると、以下のように8秒以上かかっていることがわかります。

f:id:Nageler:20200327113706p:plain

プロジェクト作ったときのサンプルでも数秒かかります、削減方法はよくわかってないです。

developers.google.com

スクロール

今回一番躓いたのがスクロールでした。文字だけのスクロールでも60fpsを達成できません。 特に画像を中に数枚置くと、動きがガクガクになりました(画像の大きさより数の方が影響が大きい)。

こちらのトップページを見ていただけるとわかると思います。

またしっかり調査したわけではないのですが、周りの反応からみて泥ChromeよりもアイポンSafariの方がスクロールに関してはスムーズだったようです。 (端末のスペックにもよるので一概には言えない)

一応対策として以下のようなStatefulWidgetを作り、スクロールの現在位置を監視しながら表示されていないViewのOpacityを0にすることで 常時読み込みのWidgetを減らしています。

少し長いのでPageでの実装は

github.com

を見てください。

現状Flutter Webでスクロールさせるのはやめたほうがいいです。許されても単純なListViewだけです(ListView.Builderを使おう!)。

パス(URL)

Webサイトにとって完全な単一ページでない限り、それぞれのページにパスを設定し直接飛べるようにしなければなりません。 Flutter Webでは基本全てが"#/"に飛ばされるのでFlutter側でパラメータの読み取り・URLの変更・画面遷移をする必要があります。

公式ではまだまだ対応が少なく(そもそもモバイルのroutingを使っているので面倒)、ライブラリも決定的なものはないようです。

私が実装したかった機能はこれらです

  • 画面遷移時にURLを変更
  • そのURLに直接アクセスするとそのページに飛ぶ(rootからツリーを辿る)
  • パラメータを渡す
  • Modalな画面遷移

画面遷移時のURL変更

これは標準で対応してくれているので、すぐにできます。やり方はMaterialAppsのRoutesに登録する方法と、MaterialPageRoutesを使う方法があります。

MaterialAppsにはroutesという引数があるので、パスと遷移先のページを指定します。

その後遷移したいときに、Navigator.pushNamde()にパスを渡して呼び出します

実はこの方法を取れば2個目の直接遷移ができます。ただしURLには注意が必要で、#/が入っていなければいけません。

https://kcs1959.github.io/2020new/#/works

routesに登録していない場合でもMaterialPageRoutesでパスを指定することでURLが変更されます。

パラメータを渡す

Flutter WebがURLから呼ばれたときの挙動は、

  1. routes内に "#"より後(hoge.hoge/#[この部分])の一致するものはあるか確認、ある場合は遷移
  2. onGenerateRoute()を呼び出す
  3. 返り値がnullの場合はhomeに指定されたページに遷移

onGenerateRoute、homeは共にMaterialAppsの引数に取ります。onGenerateRouteは引数にRouteSettingsを取り、 その中(settings.name)に"#"より後のパスが入っているのでこれをパースすることでパラメータを得られます。

後はUriクラスなどを使って適当に処理しましょう。

Modalな画面遷移

f:id:Nageler:20200327205827p:plain
こういう画面を作りたいわけだ

かっこいいのでHeroでつながったポップアップ式の画面遷移にしたい+都合上それぞれにURLを振りたい

ということを考えていました。しかし

  • 自由なレイアウトのためDialogではなくNavigatorで遷移する
  • URLを振るためにPushNamed()で遷移したい
  • ページを重ねるために背景を透明にしなければならない
  • しかしPushNamed()だと背景を透明にすることが出来ない

となっており、一筋縄ではいきません。

3つ目についてですが、Navigatorは内部でStackを利用し、Pageを上に重ねて遷移しているらしい(未確認)ので PageRouteBuilderのopaqueをfalseにすると背景を透明にでき、下のページが見える感じになります。

どうしてもパスの文字列で画面遷移したかったので、routesを保持するクラスを作ってそこからPageRouteBuilderを呼ぶようにしました。

ハイパーリンク

FlutterのTextViewはHTMLの文字とは違い<a>タグでリンクを貼ることができません。 そのためなにかWidgetにタッチイベントを付けてそこから飛ばす実装をすることになります。

Flutter Webでも外部リンクはurl_launcherを使えます。

pub.dev

タッチイベントを貼るのには

  • Card+Icon
  • Button類

辺りが分かりやすいと思います。

hover時にカーソルを変えるのはここを参考にしました。

medium.com

埋め込み

他サイト、HTML、MarkDownなどの埋め込みにはeasy_web_viewが使えました。

pub.dev

内部でdart:htmlを使っているようなので動作も軽快です。ただMarkDownについてはCSSの設定がよくわかりませんでした。

PWA

PWAは現在Flutter Webは標準で対応しています。特段の設定は必要ないようです。情報はweb/manifest.jsonに書けば反映されます。

スプラッシュ画面

パフォーマンスの項でも話しましたが、Flutter Webは起動がなかなか遅いです。白い画面が続くとブラウザバックの率が高まります。 少し考えると、main.dart.jsが読み込まれるまではindex.htmlの内容が表示されているわけで、そこに写真を貼り付ければ スプラッシュ画面の代わりとして使えます。

アナリティクス

google analyticsは、ページごとに統計をとってくれるようなので普通のWebページ用のだとHPに訪問したかどうかしかわかりません(実質単一ページのため)。

Firebaseパッケージではなんとanalyticsが使えます。

pub.dev

なのでPageを読み込む時(initState()とか?)に

analytics().logEvent("PAGE_MAIN", null);

のように埋め込んでイベントを記録することで、ページごとの訪問数を記録できます。

まとめ

  • 現状パフォーマンスにおいて実用には向かない
  • パス周りも苦労を強いられる
  • スクロールさせないSPAであればそこそこ行ける気がする
  • 細かい機能はPackageを漁れば結構見つかる
  • HTML,CSS,JSを書かなくていいのは素晴らしい

Flutter Webの将来は明るいと思います。発展させていきましょう!