「LINEのお年玉」におけるフロントエンドでの UX へのこだわり

皆さん、こんにちは。LINEでフロントエンド開発を担当しているシュウと申します。
昨年末に、年に一度の企画「LINEのお年玉」キャンペーンにて JavaScript の部分を担当させていただきました。そこで、今回は、 前回の記事に引き続き、「LINEのお年玉」での実装の裏側について紹介いたします。

今回は、小さいながらもアプリケーションの品質を大きく上げる、こまかな UX 向上施策について紹介したいと思います。

ローディングのストレス低減施策

初期ローディング時の Splash Screen の実装

今回はプロジェクト発足の段階で Progessive Loading の考えを取り入れ、コンテンツを段階的に読み込むすることを前提として設計しました。
しかし、UI 上どうしてもまとめて表示する必要のあるコンテンツもあります。今回でいうと、お年玉のメイン画面がそれにあたります。
通常、 Progressive Loading を取り入れることで、段階的な表示を踏んで読み込みのストレスを軽減できますが、まとめて表示する場合にはそうはいきません。

今回は、この問題の対処方法として、読み込み中にスプラッシュスクリーンを出すことにしました。一部のユーザーにはご覧いただけたかもしれませんが、お年玉のコンテンツが表示される前に、LINE のキャラクターが表示されます。
実装としては、単純にpublic/index.html に対して専用の CSS を入れることで実装できました。

しかし、ここで少し問題が発生します。このままでは、インターネット環境が良い場所で初期化処理が高速に終わった場合はスプラッシュスクリーンが一瞬表示されるだけとなり、チラツキが発生してしまいます。
デザイナーからの「すぐに出せるならローディングは非表示にする」という形でのデザイナー側からの要望もあり今回は初期ロード後、一定時間後にスプラッシュスクリーンを表示するように変更しました。

初期ローディング時:

ルーティングの変更時:

UX改善を通して職種を超えた協業を加速できるのは、組織の活性化という点でも大きなメリットですね。

スケルトンの実装

送受信リストの部分には react-content-loader のようなスケルトンを実装しています。今回は弊社にあるプライベートのライブラリを使って実装しました。
追加の工夫として、ユーザーアイコンのロードを待ってからフェードインするように実装しました。この実装の意図は、後述の lazy load のセクションで紹介します。

ローディングパフォーマンスを上げる

アイコンを SVG で表示する

今回タイトルエリアでのバックボタンと閉じるボタンは SVG 画像を使っています。

採用の理由

  • ベクタなので一つの画像で全端末全解像度対応できる
  • アイコンサイズであれば画像より小さい
  • HTMLに埋め込めるのでリクエストが減らせる
  • CSSとJSでコントロールできる

もちろん SVG にも欠点はありますが、アイコンぐらいであれば、SVGが持つ欠点は考慮しなくても良いと判断しました。

Code Splitting と Async Import で段階的にロードさせる

今回のプロジェクトでは Vue CLI を利用している(ここに前のブログのリンク)ため、webpack の dynamic import を利用できます。

開発中に asset size limit: The following asset(s) exceed the recommended size limit (244 KiB). This can impact web performance. という Warning が表示されたため、今回はその時点で実装を dynamic import へと置き換えました。

実際には、このように実装します。

Before:

After:

import 文ではなく import 関数を利用することで、 webpack が自動的に動的 import を行ってくれます。

UX 改善の実装時の注意点

今回は lazy load や SVG アイコン、dynamic import といった改善施策を大量に盛り込みましたが、これらの実装を行うときにはいくつかの注意点が存在します。
気をつけず安易に実装すると、バグや表示の歪さによって、かえってユーザー体験を損なう恐れがあります。
ここではそれらを防ぐ方法をご紹介します。

非同期処理の失敗時を考慮して catch を仕込む

先程 import 文を import 関数に置き換えることで、動的 import を可能にすることを紹介しましたが、動的に読み込むということは、失敗のリスクを抱えています。
先程の例でいうと、エラーが発生した場合、ページ遷移も成功せずそのままアプリケーションが停止してしまいます。

ですので、以下のようなコードを記述して対処する必要があります。

実際のアプリケーションでは、複数のルーティングに対してこれを考慮する必要があるため、以下のようなラッパー関数を用意しました。

tips: ここの webpackInclude は Magic Comments と呼ばれており、webpackへとエイリアス設計を提示するために必要となります。

lazyload する際は、事前に画像の高さを指定する

次に lazyload についてです。

キャンペーンサイトは画像が非常に多い都合上、 lazyload されているかどうかでパフォーマンスが大きく変わります。そのため、今回の例でもファーストビューに相当する部分以外は全て lazyload の対象としています。
しかしながら、 <img> を lazy load する場合、読み込み内容が確定するまで高さが計算されません。よくウェブサイトを閲覧するときにあとから「ガクッ」と下にスクロールしてしまうことがありますが、ユーザーにとって良い体験ではないでしょう。
そのため、今回は以下のようなコードを記述して、事前に高さを渡すようにしました。

その上で、ローディング完了時はフェードインさせることで、より違和感のない lazyload を実現しました。

状況に応じた共通コンポーネントの出し分け

最後に SPA を開発するときにありがちな共通表示コンポーネントの取り扱いです。アプリケーション本体ではヘッダーを表示したいけれど、トップページでは表示したくないというシチュエーションはありがちではないでしょうか。
そういった場合、共通のレイアウトがあるところで認証待ちなどを行うと問題が生じます。このように、共通のタイトルバーが表示されたあとに、フルスクリーンなコンテンツが表示されてしまっています。

Vue Router は認証の判定が終わるまではリダイレクト前のページにいるので当然といえば当然ですが、単純にコンポーネントの状態で解決できるというわけでもありません。そのために

アニメーションと Vue Router に対応したポップアップモーダルの実装

最後にちょっと込み入った実装として、 Vue Router とアニメーションとシームレスに連携するポップアップの実装をご紹介します。

Popup へのトランジションの設定

まずはポップアップモーダルにフェードイン・フェードアウトをつけるところからです。よくある実装ですね。
ポップアップを実装するときは、 overlay の背景とモーダルのメインコンテンツを別々のアニメーションとして実装することが重要です。こうすることで、コンテンツは拡縮しながらも、背景は色ベースでフェードするだけのコンテンツがつくれます。

Vue Router と連携してバックキーに対応する

これで単純なポップアップとしては成立しますが、実はこれだけでは問題があります。
先程のサンプルではただのコンポーネントとしてポップアップを実装したため、Android のバックキーなど、ブラウザや OS 側の History 操作が行われた場合、意図しないページ遷移が発生します。今回の場合、「LINEのお年玉」の外のページにアクセスすることがあるため、別サイトにそのまま飛んでしまう。といった問題が起こります。
これを防ぐために、今回はポップアップをルーティングの一部として実装しました。以下のサンプルをみてください。

※ Code Sandbox の iframe では Router が正しく動作しないため、新しいタブで閲覧ください。

ここでの実装のポイントは 2 つあります。

まずひとつは router-view でラップしたことです。これによって、openPopup の実行時に DOM 要素を生成し Popup.vue の mounted にて開くことで、アニメーションを保つことができます。

そしてもう一つのポイントは History 操作です。router.back や router.go(-1) でも History の操作はできますが、別 Origin からのアクセスの場合に別のサイトに移動してしまうため、今回は自分で管理しているフラグをベースに History 操作を行いました。

ポップアップを閉じるトランジションを動作させる

これで実装できたように見えますが、このままでは開くときのトランジションだけ動作します。これは Popup のコンポーネントが消えてしまうことで、 DOM 上から完全に姿を消してしまうことが原因です。
こういったときは、 Vue.js のトランジションをイベントとしてフックして解決します。

実装としては、はじめに Promise を活用してトランジションが完了するまで待ち、その上でもしブラウザ側で History が戻る場合には、先にPopupを閉じたうえでトランジションを待ってからルーティングを更新するようにしました。

おわりに

「LINEのお年玉」では、実際にこれらの実装によって、ユーザーにとって良い体験を提供できました。
フロントエンドでの小さな工夫によって、 SPA の UX は大きく変わります。自主的に改善していくことが重要だと感じました。

PR

LINE株式会社では、 Vue.js や TypeScript に詳しいエンジニアを募集しています。
ご興味のある方はぜひ以下からご応募ください。

フロントエンドエンジニア / フロントエンド開発センター

Related Post