カスタムフック
Vercel謹製のhookがある
github.com
参照 (ref)
messagesRef: メッセージリストの一番下の要素を指す
scrollRef: スクロール可能なコンテナ要素を指す
visibilityRef: 特定の要素の可視性を監視するための要素を指す
State
isAtBottom: スクロール位置がリストの一番下にあるかどうかを示す
isVisible: visibilityRefが指す要素が画面上に表示されているかどうかを示す
messagesRefに紐付けられた要素までスクロールする。 「クリックで最下部までスクロールする」ボタンなどで利用する
自動的に最下部までスクロールされるやつ
isAtBottom, isVisibleに変更があった場合に実行される
scrollIntoView: scrollIntoView() が呼び出された要素がユーザーに見えるところまで要素の親コンテナをスクロールするメソッド
useEffect(() => {
if (messagesRef.current) {
if (isAtBottom && !isVisible) {
messagesRef.current.scrollIntoView({
block: 'end'
})
}
}
}, [isAtBottom, isVisible])
(ほぼ)最下部までスクロールされたかどうかを更新する
- スクロールが発生するたびに更新する
- target.scrollTop + target.clientHeight >= target.scrollHeight - offsetの解釈にやや悩むが、要は最下部から数えてoffset分の高さ以内までスクロールされているかどうかを判定している(isAtBottomとあるから最下部までスクロールされているかどうかのBool値のように見えるが、本当にBottomであれば逆にスクロールはできない)
- offsetは書き込み欄の分を指定する
参考:
Element: clientHeight プロパティ - Web API | MDN
useEffect(() => {
const { current } = scrollRef
if (current) {
const handleScroll = (event: Event) => {
const target = event.target as HTMLDivElement
const offset = 25
const isAtBottom =
target.scrollTop + target.clientHeight >= target.scrollHeight - offset
setIsAtBottom(isAtBottom)
}
current.addEventListener('scroll', handleScroll, {
passive: true
})
return () => {
current.removeEventListener('scroll', handleScroll)
}
}
}, [])
IntersectionObserverで画面上に表示されているかどうかを更新する
useEffect(() => {
if (visibilityRef.current) {
let observer = new IntersectionObserver(
entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
setIsVisible(true)
} else {
setIsVisible(false)
}
})
},
{
rootMargin: '0px 0px -150px 0px'
}
)
observer.observe(visibilityRef.current)
return () => {
observer.disconnect()
}
}
})
使い方
visibilityRefを指定しているdivは見えているかどうか判定用の空要素
<div ref={scrollRef}>
<div ref={messagesRef}>
<ChatList messages={messages} />
<div ref={visibilityRef} className="w-full"/>
</div>
</div>