Reactで自動スクロールを使う

## はじめに
自動スクロールは多くのサービスに使用されていて、多くの実装方法があったので、忘備録としてもまとめてみました。他のサイトでもよく紹介されているのが下記のような気がします。
・[react-scroll](https://www.npmjs.com/package/react-scroll)

window
  ・[scrollTo](https://developer.mozilla.org/ja/docs/Web/API/Window/scrollTo)
  ・[scrollBy](https://developer.mozilla.org/ja/docs/Web/API/Window/scrollBy)

Element
・[scrollIntoView](https://developer.mozilla.org/ja/docs/Web/API/Element/scrollIntoView)
・[scrollTo](https://developer.mozilla.org/ja/docs/Web/API/Element/scrollTo)
・[scrollBy](https://developer.mozilla.org/ja/docs/Web/API/Element/scrollBy)

## react-scroll
Reactのコンポーネントに関して自動スクロールが実装できるライブラリで、様々なスクロール方法が使用できます。今回は一部だけ紹介しますが、気になる方は[npmのドキュメント](https://www.npmjs.com/package/react-scroll)見てください。使うメリット・デメリットは下記かなと考えてます。
- メリット
    - 様々なスクロール手法が使用できる
    - 細かい自動スクロールの設定ができる
    - 細かい自動スクロールが簡単に実装できる
- デメリット
    - Topだけに移動するなど単純な自動スクロールに関して使う場合、標準APIの方が分かりやすい
    - 機能が多いためキャッチアップを少なからず必要となる
    - メンテされなくなる可能性がある※[github](https://github.com/fisshy/react-scroll)を見ると最近マージされている
    - name管理をする必要がある

### 実際の画面
![auto-scroll.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3659014/63e01ea5-175a-b23f-4a0c-6271359670da.gif)



### 実装
TanStack Routerとstyled componentsは使っていて分かりづらい部分があるが、雑に結果だけ書くと下記のようになります。
固定されたヘッダ分やtop表示か中央表示などの細かい操作ができます。(下記の場合、奇数番号のボタンを押下した場合top表示、それ以外は中央表示。)
```
import { createFileRoute } from "@tanstack/react-router";
import styled from "styled-components";
import { Element, scroller } from "react-scroll";

export const Route = createFileRoute("/react-scroll/")({
  component: () => {
    // 自動スクロール
    const onScroll = (isTop: boolean, name: string) => {
      scroller.scrollTo(name, {
        duration: 1000,
        delay: 100,
        smooth: true,
        offset: isTop ? -128 : -innerHeight / 3,
      });
    };
    // 表示するリスト
    const count = 10;
    const list = [];
    for (let i = 0; i < count; i++) {
      list.push(`${i + 1}番目`);
    }
    return (
      <Wrap>
        <Header>
          <Content>ヘッダ分の高さ</Content>
        </Header>
        {list.map((v, i) => (
          <Element name={`scroll-target${i}`}>
            <ScrollComponent>
              {v}
              {i + 1 < list.length && (
                <Button
                  onClick={() => {
                    const isTop = i % 2 === 0;
                    onScroll(isTop, `scroll-target${i + 1}`);
                  }}
                >
                  次へ
                </Button>
              )}
            </ScrollComponent>
          </Element>
        ))}
      </Wrap>
    );
  },
});

const ScrollComponent = styled.div`
  height: 300px;
  width: 100%;
  background-color: #fee;
  border: 2px solid;
  border-radius: 10px;
  margin: 128px auto;
  padding: 8px;
`;

const Header = styled.div`
  width: 100%;
  height: 64px;
  background-color: #ffe;
  position: fixed;
  top: 0;
  right: 0;
  border-bottom: 1px solid;
  padding: 10px;
`;

const Wrap = styled.div``;

const Content = styled.div`
  padding: 20px;
`;

const Button = styled.button`
  width: 128px;
  height: 64px;
  background-color: #ffe;
  border-color: #ffe;
  border-radius: 8px;
`;

```

### 解説
大した解説でもないですが、やっていることはreact-scrollの`Element`で定義された`name`をターゲットに`scroller.scrollTo`の設定に基づいて自動スクロールしているという感じです。今回は、offset値を変更することでtop表示か中央表示かを判別しています。
今回は、自動スクロールの手法のお話なのでreact-scrollの細かい説明は割愛します。

## window(scrollTo/scrollBy)
ここからは、実際の画面を作らずメリット・デメリットだけ記載しようかと思います。気になった方はコンソールを開いて実際にブラウザ上で試してください。mdnのドキュメントは[scrollTo](https://developer.mozilla.org/ja/docs/Web/API/Window/scrollTo) / [scrollBy](https://developer.mozilla.org/ja/docs/Web/API/Window/scrollBy)となります。Element指定でなく座標指定となるので、細かい挙動には向いてないです。実装としては`window.scrollTo(0,0)`などで指定してあげるだけです。
- メリット
    - 簡単に実装でき仕様も直感的である
    - 標準的に使えるので、基本的に使える(推奨ブラウザなどは別途参照してください)
- デメリット
    - 細かい挙動が難しい

やはり簡単な分、細かい動きができないのでTopだけにBottomに移動させるみたいな決まった動きの場合のみ使うのが適切なパターンかなと思ってます。
  
## Element(scrollIntoView/scrollTo/scrollBy)
mdnのドキュメントは[scrollIntoView](https://developer.mozilla.org/ja/docs/Web/API/Element/scrollIntoView) / [scrollTo](https://developer.mozilla.org/ja/docs/Web/API/Element/scrollTo) / [scrollBy](https://developer.mozilla.org/ja/docs/Web/API/Element/scrollBy)となります。Elementに紐づいて動くので、Elementを画面中央に表示させたいなど、Elementごとに使うことができるためwindowオブジェクトよりも汎用的に使えます。使い方は雑多ながら下記に記載します。
```
// hogehogeの要素を画面topに表示する
const scroll = document.getElementById("hogehoge").scrollIntoView({block:'start'})
〜
<button onClick={()=>{scroll()}}>ボタン</button>
〜
<div id='hogehoge'>huga</div>
```
- メリット
    - 簡単に実装でき仕様も直感的である
    - Elementに応じて使用できるのでwindowよりも細かく指定できる
- デメリット
    - react-scrollのような細かい挙動が難しい
    - 細かい挙動のドキュメントが少なく画面全体を覆うような要素に対して中央表示させる場合など実際に試してみないとわからない
    - scrollToは使えないみたいな記事や実際に使えなかったので調査する必要がある(今回は割愛)
    - id管理をする必要がある(重複すると自動スクロールしない)

`scrollIntoView`に関してはオプションにdurationやoffset値が取れないため細かい動きは難しく、ページ内の特定箇所に自動スクロールなどの実装に向いていると考えています。細かい挙動や仕様で使う場合はreact-scrollの方がよさそうですね。