跳到主要内容

滚动加载更多

· 阅读需 3 分钟

记录下聊天页面与滚动相关的两个功能的实现方案

  • 滚动至顶部加载更多
  • 自动滚动至新消息

封装 useChatScroll hook

接收聊天页面容器 div、bottom div(chat 最下方的空 div)、shouldLoadMore、loadMore 方法、count(数据总数)五个参数

useChatScroll({
chatRef,
bottomRef,
// useChatQuery hook 返回的请求更多的方法
loadMore: fetchNextPage,
// 没有正在请求下页数据,并且存在下页数据时,可以加载更多
shouldLoadMore: !isFetchingNextPage && !!hasNextPage,
count: data?.pages?.[0]?.items.length ?? 0,
})

加载更多

该 Hook 中主要有两个 useEffect 组成

第一个主要负责监控聊天框的滚动事件。当容器 div 的滚动条位于顶部(scrollTop = 0)且可以加载更多时,调用 loadMore 即可;

useEffect(() => {
const topDiv = chatRef?.current

const handleScroll = () => {
const scrollTop = topDiv?.scrollTop

if (scrollTop === 0 && shouldLoadMore)
loadMore()
}

topDiv?.addEventListener('scroll', handleScroll)

return () => topDiv?.removeEventListener('scroll', handleScroll)
}, [shouldLoadMore, loadMore, chatRef])

自动滚动至新消息

第二个 useEffect 主要负责自动滚动聊天框以保持最新的消息可见

使用 hasInitialized state 作为标记,第一次渲染时将其设置为 true,并执行一次 autoScroll(以确保在初始化时自动滚动)

autoScroll 就是将 bottom div scrollIntoView

  • 优化

后续当 count 变化时,检查聊天视口底部到容器底部的距离(总滚动高度 - 滚动过的距离 - 可视高度)。如果距离小于等于100像素,表示接近底部,此时执行 autoScroll。防止当用户在浏览历史信息时,突然滚动到最下方

const [hasInitialized, sethasInitialized] = useState(false)

useEffect(() => {
const bottomDiv = bottomRef?.current
const topDiv = chatRef.current
const shouldAutoScroll = () => {
if (!hasInitialized && bottomDiv) {
sethasInitialized(true)
return true
}
if (!topDiv)
return false

const distanceFromBottom = topDiv.scrollHeight - topDiv.scrollTop - topDiv.clientHeight
return distanceFromBottom <= 100
}

if (shouldAutoScroll()) {
setTimeout(() => {
bottomRef.current?.scrollIntoView({
behavior: 'smooth',
})
}, 100)
}
}, [bottomRef, chatRef, count, hasInitialized])