追加、変更。下記の謝辞参考

Angularで遅延してスクロール

画面の描画後に特定位置まで自動でスクロールさせたい場合にどうしたら良いかを実装した。

実装した例はこれである。https://yschedule.ement.dev/l/v_kurore/ksonArk

ngAfterVewInitでscrollを発火させればよいと思ってたが、ずれてしまう。その原因は

  • ngForによるループ処理でデータ量が可変となる。
  • ngFor内に画像があり、レスポンシブデザインのため、高さが可変となる。

DOMが生成されていても、画像の読み込みには時間が掛かる。そのためscrollが思った場所にいかずにズレるようだ。

HTMLが完成するまでの流れ

これは、憶測である。きちんと検証した訳では無いことを先に宣言しておく。Chrome Developer ToolsのNetworkを見ても、DOM描画、HTMLとしての反映までの同期しながらの検証方法がわからなかったのと、正直面倒だったから。

動的にDOMが作成され、ブラウザに描画されるまでの流れは、下記の通りだと思う。

HTMLが完成するまでの流れ

実際には、どのタイミングでHTML描画(ブラウザに反映)されているか不明。おそらく、画像のダウンロードに関してはimgタグが発見されて、いい感じにタグが閉じられたときに画像のリクエストを開始しているように思える。(実際には知らん。)

リクエストした画像ファイルがリクエストした順番通りに帰ってくる保証はないにしろ、近しい結果にはなるだろうと信じたい。

loadイベント

画像がロードされたかはloadイベントを使うことで検知することができる

load イベントは、ページ全体が、スタイルシートや画像などのすべての依存するリソースを含めて読み込まれたときに発生します。これは DOMContentLoaded が、ページの DOM の読み込みが完了すれば、リソースの読み込みが完了するのを待たずに発生するのと対照的です。

https://developer.mozilla.org/ja/docs/Web/API/Window/load_event

実装

imgのloadを利用する。

(修正済み):謝辞参考

hoge.component.ts

コンポーネント内にスクロールさせる処理を記載する。200msの遅延は保険としているため不要かもしれない。

  goScroll(scroller: HTMLElement) {
    window.setTimeout(() => {
      scroller.scrollIntoView({ behavior: 'smooth' });
    }, 200);
  }
1
2
3
4
5

hoge.component.html

*ngForには最後のループなのかを判定できるlast変数が実装されている。画像がロードされていて、かつ最後のループの場合に、goScrollメソッドを発火させる。last === true時のみ実行したいのだが、false時の処理も記載しないといけないので、適当にtrueでも返しておけば良い。goScrollイベントにisLast引数を追加する方が丁寧かもしれないけど。

<div>
    <p>なにかの文字列.............</p>
</div>
<div *ngFor="let i of items: let last = last">
    <img src="i.img" (load)="last ? goScroll(scroller): true"><!-- 画面ロードしたら発火する -->
</div>
<div #scroller></div><!-- スクロールさせたい場所 -->
<div>
    <p>なにかの文字列.............</p>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13

まとめ

(修正、追加):謝辞参考

この処理でズレが生じないかというと確実ではない。

例えば、ループ内の画像サイズにばらつきがある場合に、lastの画像が先にロードされる場合もある。

item = [
  '10Mの画像' , '10Mの画像', '10Mの画像', '10Mの画像' , '10byteの画像' ←最後が軽い
]
1
2
3

画像によるズレを回避するには、全部の画像をチェックする方が確実。ブラウザの描画が完成されているためズレはうまれないはずだ!

追記:謝辞

ありがとおおおお。