[Angular]任意の位置にスクロールされた状態でページを表示

Kana Otawara
nextbeat-engineering
Aug 19, 2021

こんにちは、ネクストビートのエンジニアの太田原です。

保育施設向けICTシステム「キズナコネクト」の開発チームに所属しています!🐧

今回はAngularで、任意の位置にスクロールされた状態でページを表示するような挙動を実現できたので、実装方法を1つのアイデアとして紹介します。

実現したいこと

Google Calendarのスマートフォンアプリ版の「スケジュール」表示モードのような挙動を実現したかったです。
具体的には、今日の日付までスクロールされた状態で初期表示され、上向きにスクロールすると過去のものを遡れる、といったものです。

Google Calendar 「スケジュール表示」

実装したAngularコードとデモ

Google Calendarを模したような、予定の一覧を表示しています。
スクロール先に指定した日付の予定が、水色背景になっています。

app.component.htmlにおいて、スクロール先の予定にのみScrolledPositionComponentを表示するような*ngIfの条件式を記載しています。

ScrolledPositionComponentは、ビューの中身は何もない(幅も高さもない)です。
ngAfterViewInitコールバック内でこのコンポーネントの表示された位置までスクロールさせるという処理を行っています。

constructor(
private elementRef: ElementRef,
) { }
ngAfterViewInit(): void {
this.elementRef.nativeElement.scrollIntoView()
}

この部分は、ngAfterViewInitなのがミソです。ngOnInitでは、ビューの初期化が終了していないので、正確なスクロール位置を取得するにはngAfterViewInit内に書く必要があります。

scrollIntoViewを使用することで、スクロール先の絶対位置pxを計算するなどの煩雑な処理を避けることができました🙌

この実装にした理由

URLフラグメントを使用したくなかったため

特定の位置にスクロールされた状態でページを初期表示する方法というと、

  • スクロール先の要素にidを指定し
  • URLの末尾に「#idの名前」(フラグメント)を付ける

のがよく知られていると思います。最初にこれを思いつきましたが、

  • URLをシンプルにしたかった
  • 実際の機能では「今日」という日によって変わる情報に応じてスクロールをさせる必要があったので、ルーティングの制御が複雑になりそうだった

といった理由から、使用しませんでした。

画面が動的に切り替わる機能だったため

わざわざスクロール先の位置に別のコンポーネントを設置したのも事情があります。
デモでは1月分のデータしか表示していませんが、実際開発した機能では月ごとに画面表示を切り替えるようになっていました。
月が切り替わるたびに適切な位置(今月の場合は「今日」に、他の月の場合は月の最初)にスクロールさせる必要があったのですが、これはデモのAppComponent内で完結させることが難しかったです。なぜなら、

  • AppComponentのngAfterViewInitは、ページの初期化時点でしか実行されず、月の切り替えごとには実行されない
  • かと言って、ngAfterViewCheckedはビューが変更されるたび実行され、頻度が多い上に、いつスクロール位置の描画が完了しているかが判断できない

だからです。
ScrolledPositionComponentは*ngIfで出し分けされているので、月の切り替えごとに再生成され、一度だけngAfterViewInitが呼ばれるということで、スクロールさせる処理を記述するのに都合が良かったのです。

今回学習できたAngularの機能

Angularのライフサイクルフック

普段使うのはngOnInitかngOnDestroyくらいなので、他のものについて改めてしっかり確認するいい機会になりました。

ngAfterViewInit/ngAfterViewCheckedはぼんやりとビューの初期化や変更に関連して呼ばれるくらいの認識はありましたが、ngOnInitではビュー初期化は完了していないのでこういう特殊なことをするときは気をつけないといけないと思いました。

また、ngAfterViewInit/ngAfterViewCheckedはコンポーネントそのものだけでなく、子ビューやディレクティブも含めて検知しているということも初めてきちんと知りました。たくさん子コンポーネントを設置しているようなコンポーネントであれば、ngAfterViewCheckedは高頻度で実行されることもあり、パフォーマンス問題にならないように注意が必要です。

Angular 日本語ドキュメンテーション — ライフサイクルフック

スクロール周りのAngularの機能

今回は使用しませんでしたが、Router ScrollerというAngularのルーティングにスクロール関連の設定をできる機能があります。

Angular v6.1で導入されるRouter Scrollerの紹介 — lacolaco

こちらの記事が詳しいですが、今回使用しなかった方法であるURLフラグメント(#)でその位置までスクロールして表示する仕組みをAngularのRouterがサポートしています(Anchor Scrolling)。
他にも、Scroll Position Restorationというスクロール位置の記録と復元もサポートされています。

おわりに

本記事では、Angularで、

  • URLフラグメントを使用しない
  • ビューの動的な変更に対応できる

という条件を満たし、任意の位置にスクロールされた状態でページを表示する方法をご紹介しました。

実装方法を検討したり、記事を書く中で、ライフサイクルやルーティングについて正確な仕様について知ったり、できることを新たに知ることができました。
まだまだ知らないことが多いので、改めて体系的な知識を付けたいなと感じました。

読んでいただきありがとうございました🙇‍♀️

We are Hiring!

株式会社ネクストビートでは

「人口減少社会において必要とされるインターネット事業を創造し、ニッポンを元気にする。」
を理念に掲げ一緒に働く仲間を募集しております。

バックエンドにはPlay Framework(言語はScala)、フロントエンドの開発には主にAngularを用いています。フルスタックに開発したい!という方のご応募をお待ちしております。

--

--