Firebaseと画像とUXを考える


どうもFirebase Japan User Groupオーガナイザーの@1amageekです。
この記事ではUX向上のための画像の取り扱い方について解説しようと思います。

サービスと画像

画像のロードはサービスのUXに大きな影響を与える

WEBでは、1秒の遅れでCVRが7%ダウンすると言われています。Instagramがもし画像のロードに1秒もかかっていたとすれば、おそらくここまでは大きなサービスにはならなかったでしょう。
画像の取り扱いには、時間をかけてUXを向上させる意味があります。

ロード速度も重要だが、待ち時間こそ重要

画像のロードは、サイズと通信速度に依存します。つまり技術的観点からすれば、とにかく画像を小さくすること以外は、解決方法はありません。

既存のあらゆるサービスは、待ち時間をいかに低減させるかを工夫してUXを向上させてきました。この記事ではその手法に焦点をあてて紹介しようと思います。

また、FirebaseではClient Side JoinによりREST APIを利用した場合より問題が顕著になる場合があります。まずそこについて言及します。

Firebaseにおける画像の取り扱い

CloudFirestoreではデータベースの特性上、画像の取得までに時間がかかる場合があります。主な原因はClient Side Joinにあります。
CloudFirestoreの通信速度の中央値は約200ms ~ 300msほどです。
Client Side Joinにより直列的な通信が複数回発生した上に画像のロードが発生したとすると1000msを超えてしまう場合もあります。

データ構造を最適化して、直列的な通信(複数回のClientSideJoin)を防ぎましょう。

データ構造の最適化

ここでは、次の画像のようにGroupに所属するUserthumbnailをリスト表示するUIを考えて見ましょう。

まずはアンチパターンを紹介します。

アンチパターン

Userに注目してください。thumbnailImageImageによって抽象化されています。

class Group: Object {
    var users: ReferenceCollection<User> = []
}

class User: Object {
    var name: String?
    var thumbnailImage: Relation<Image> = .init()
}

class Image: Object {
    var url: URL?
}

このパターンでは、3回の通信が発生した後に画像のURLを取得でき、ほぼ1秒後に画像がロード可能な状況になります。

解決方法

可能な限りメディア系のモデルの抽象化はしないようにしましょう。

class Group: Object {
    var users: ReferenceCollection<User> = []
}

class User: Object {
    var name: String?
    var url: URL?
}

しかし、これにもトレードオフはあります。次のように大量な画像を複数保持する場合は別です。なぜならUser自体が肥大化していくことを意味し、データベース全体の性能を下げることになります。

class Group: Object {
    var users: ReferenceCollection<User> = []
}

class User: Object {
    var name: String?
    var url: URL?
    var images: [URL] = []//数枚保存するだけなら問題ない。
}

大量と言うのは無限に増加可能性がある場合です。例えばGoogle Photoのようにユーザーの撮った写真を保存するようなサービスの場合です。このようなプロパティをGrowth Propertyと呼びます。

Growth PropertyについてはRealtime Database Designで紹介してるのでこちらをご覧下さい。

数枚保存するくらいであれば全く問題ありません。

Pringでも複数のFileを配列で保存できるようにしています。

画像ロードのUX改善方法

今回は以下の手法について言及します。

  • キャッシュを利用する
  • 動的Placeholderを利用する
  • Preheatingを利用する
  • Multiplex Image Loadingを利用する
  • HEIF

ここからはFirebaseはあまり関係ありません。一般的なサービスでも十分に役にたつテクニックですので参考にしてもらえると嬉しいです。またこの手法はPinterestで多く利用されているのでPinterestを例に説明していこうと思います。

キャッシュを利用する

すでにサービスで実装されていることがほとんどなので深くは説明しませんが、簡単に言うと一度取得したコンテンツをローカルで保持し、再利用する技術です。

あえて注意点をあげるとすれば以下の2点です。

キャッシュを保持する期間

同じURLで別の画像をロードしたいときに困ります。同じURLで画像をロードすることは避けましょう。

ローカルリソースを圧迫する

たくさんの画像を取り扱うサービスでサイズの大きい画像をキャッシュし続けると困ります。必ずキャッシュサイズには制限を設けてFILOになるように設計しましょう。

動的Placeholderを利用する

動的PlaceholderはFacebook, Slackで見ることが出来ます。

アニメーションすることで、コンテンツのロードまでの待ち時間のストレスを緩和することが出来ます。また、描画的な観点から見ても、同じ大きさのセルのあらかじめ用意しておくことでコンテンツが挿入された時にレイアウトでガタつくのを低減できます。

Client Side Joinによって画像以外のコンテンツもロードできてない可能性があるFirebaseでは、動的なPlaceholderはとても相性の良い技術です。

またPinterestFirst Viewでは次の画像のように5個程度のコンテンツしかロードしてません。

5個と言うのはちょうどFirst Viewを覆うことができる量です。2回目のフェッチで30個ほどのコンテンツをロードしています。ここにも待ち時間を少なくするための工夫が入っています。

Preheatingを利用する

Preheatingの歴史

Preheatingは聞き慣れない言葉だと思います。この言葉が使われたのはiOS8で登場したPhotoKitのサンプルコードです。実際はそれ以前にFacebookのScott Goodson氏によって発表されました。
彼はAppleでApplication開発やFrameworkの開発を手がけたあとFacebookでTextureの前進となるAsyncDisplayKitを開発しました。現在は、Instagramを経てPinterestで開発を続けているようです。

当時の発表がこちらです。
https://youtu.be/ZPL4Nse76oY?t=24m23s

Preheatingとは

簡単に言うと、コンテンツが描画される前にコンテンツを準備する技術です。

わかりやすい動画があるのでこちらをご覧ください。
https://www.youtube.com/watch?v=wrctPJxskhI

描画前にコンテンツを取得するPreheatingは、FirebaseのClient Side Joinととても相性の良い技術です。

iOSでの利用

iOSではiOS10からPrefetchingが利用可能です。しかし、AutoLayoutでこれを利用するとメインスレッドで大量のセルをレイアウトすることになりフレーム落ちやカクついた動作になるので注意が必要です。iOS12からはレイアウトのタイミングが最適化されるので改善されるようです。

Multiplex Image Loadingの利用する

Multiplex Image Loadingとは

この技術は複数サイズにリサイズされた画像を並列にロードしていく技術です。
次の例では3つの画像サイズを用意して、各々を同時にロードしています。

個人的にはこの技術は非推奨ですが、ほとんどの開発環境では複数サイズの画像をすでにサーバーに用意しているのでいますぐUXを改善させるには、この方法を選ばざるを得ない状況だと思います。
推奨しない理由は、利用しないデータを取得しまっているところです。
実は、この問題を解消する技術はかなり古くから存在します。Progressive JPEGです。
できる事であればこちらを利用することをお勧めします。

Progressive JPEGとは

一般的なJPEGは上から順にデータをロードしていきます。Progressive JPEGではまず、画像全体を解像度の低いデータをロードし、徐々に解像度を上げていくようにデータをロードします。

Multiplex Image Loadingが複数ファイルで同じようなことを実現しているのに対して、Progressive JPEGは1ファイルでこれを実現します。

Progressive JPEGはサイズの大きい画像をロードする場合は有効です。
サムネイルなどの小さいサイズの画像は適切なサイズの画像を準備しましょう。

HEIF

HEIFとは

High Efficiency Image File Formatの頭文字をとってHEIFと呼ばれています。特徴は画像データだけでなく豊富なメタデータを含むことができます。

Appleが一番詳しく説明しているのでこちらをどうぞ
https://developer.apple.com/videos/play/wwdc2017/513/

HEIFの最大の特徴は圧縮率にあります。JPEGよりもさらにファイルサイズを半分程度にできます。
さらにthumbnailというメタデータを指定することです。高画質な画像を低解像度で表示することもできます。

こちらも個人的には非推奨です。ライセンスに問題があるので自由に利用するのは難しいためです。

まとめ

今後もロード時間を低減するための工夫は、次々考案されていくはずです。
サービスを開発するエンジニアであればひたすらUXの向上に励みたいですね💪🏻

Firebaseに興味が沸いた方は、こちらの記事もおすすめです。
あなたが知らないFirestoreのコアテクノロジー