⛓️

React 一番安定する自動スクロール

2021/03/12に公開
1

画面ローディング時にhtmlの一番下までスクロールしたい

こんちには、ハトです。業務で自動スクロールが必要になったので、いくつか方法を考えました。

  • react-scrollでuseEffect(useLayoutEffect)ないでscrollToBottom()する
  • react-windowというリストライブラリを使ってuseEffect時に一番下のアイテムまでscrollToItem()する
  • リストの最後にdivタグをおき、refを取得。useLayoutEffect内で、ref.current.scrollIntoView()をする

useRefに関してはこちらの記事もご参考ください。
https://zenn.dev/dove/articles/e2d962e9d69e20

結論。refでscrollIntoViewをするのがよい

ミニマム

const List = () => {
  const scrollBottomRef = useRef<HTMLDivElement>(null);
  
  useLayoutEffect(() => {
  
    // 以下はtypescriptの書き方。jsの場合は
    // if(scrollBottomRef && scrollBottomRef.current) {
    //   scrollBottomRef.current.scrollIntoView()
    // }
    scrollBottomRef?.current?.scrollIntoView();
  }, []);
  
  const items: string[] = [];
  return (
    <div>
      {items.map(item => (<div>{item}</div>))}
      <div ref={scrollBottomRef}/>
    </div>
  )
}

データをフェッチしたあとの場合

const List = () => {
  const [isLoading, setIsLoading] = useState(false);
  const [items, setItems] = useState<string[]>([]);
  const scrollBottomRef = useRef<HTMLDivElement>(null);
  
  // データフェッチ
  useEffect(() => {
    // unmount時にはsetStateしない。フラグ。
    let isUnmounted = false;
    const load = async () => {
          const items = await axiosLikeLibrary.get('https://example.com/items');
	  
	  if(!isUnmounted){
	     setItems(items)
	  }
    };
    
    load();
    
    return () => {
      isUnmounted = true;
    }
  }, []);
  
  // フェッチ後自動スクロール
  useLayoutEffect(() => {
  
    // 以下はtypescriptの書き方。jsの場合は
    // if(scrollBottomRef && scrollBottomRef.current) {
    //   scrollBottomRef.current.scrollIntoView()
    // }
    scrollBottomRef?.current?.scrollIntoView();
  }, []);
  
  const items: string[] = [];
  return (<>
    {isLoading ? (
      <div>ローディング中...</div>
    ) : (
      <div>
        {items.map(item => (<div>{item}</div>))}
	{/* 一番下までスクロールするためのdiv↓ */}
        <div ref={scrollBottomRef}/>
      </div>
    )}
  </>)
}

他の選択肢はなぜだめなのか

react-scrollでuseEffect(useLayoutEffect)ないでscrollToBottom()する

これは難しいです。useEffectないではreact-scrollの animate scroll 機能は動きませんでした。結局useEffectないでsetTimeoutでいくらか時間をもうけてレンダリングをまってスクロールしていたのですが、レンダリングが終わる時間はリストの長さによってまちまちなので、そのやり方もうまくいきませんでした。

いかは関連するissue
https://github.com/fisshy/react-scroll/issues/430

Maybe the element isn't declared in the tree yet.
よくわからないのですが、useEffectないではツリーはまだ宣言されていない?みたいです。

react-windowというリストライブラリを使ってuseEffect時に一番下のアイテムまでscrollToItem()する

これでも実現できました。くわしくは以下
https://zenn.dev/dove/articles/4649c8e2dc3cd8

ただし、スクロールするだけにreact-windowを利用するのはオーバーすぎたのでやめました。

Discussion

yanskunyanskun

scrollIntoView 知らなかったです...
ありがとうございます。

読んでたら、typo を見つけてしまいました...

- const scrollBottomRef = useRef<HTMLDivEelement>(null);
+ const scrollBottomRef = useRef<HTMLDivElement>(null);