feat: 增加聊天列表滚动到底部按钮

pull/64/head
moonrailgun 2 years ago
parent eb3b5f9c00
commit 7f1b475f69

@ -1,7 +1,8 @@
import React, { useCallback, useEffect, useRef } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useMemoizedFn, useSharedEventHandler } from 'tailchat-shared';
import { ChatMessageHeader } from './ChatMessageHeader';
import { buildMessageItemRow } from './Item';
import { ScrollToBottom } from './ScrollToBottom';
import type { MessageListProps } from './types';
/**
@ -9,6 +10,7 @@ import type { MessageListProps } from './types';
* 1px
*/
const topTriggerBuffer = 100;
const bottomTriggerBuffer = 40;
/**
*
@ -17,6 +19,7 @@ export const NormalMessageList: React.FC<MessageListProps> = React.memo(
(props) => {
const containerRef = useRef<HTMLDivElement>(null);
const lockRef = useRef(false);
const [showScrollToBottom, setShowScrollToBottom] = useState(false);
const scrollToBottom = useMemoizedFn(() => {
containerRef.current?.scrollTo({ top: 0, behavior: 'smooth' });
@ -44,9 +47,10 @@ export const NormalMessageList: React.FC<MessageListProps> = React.memo(
return;
}
if (containerRef.current.scrollTop === 0) {
if (-containerRef.current.scrollTop <= bottomTriggerBuffer) {
// 滚动到最底部
lockRef.current = false;
setShowScrollToBottom(false);
} else if (
-containerRef.current.scrollTop + containerRef.current.clientHeight >=
containerRef.current.scrollHeight - topTriggerBuffer
@ -57,6 +61,7 @@ export const NormalMessageList: React.FC<MessageListProps> = React.memo(
// 滚动在中间
// 锁定位置不自动滚动
lockRef.current = true;
setShowScrollToBottom(true);
}
}, [props.messages]);
@ -72,6 +77,8 @@ export const NormalMessageList: React.FC<MessageListProps> = React.memo(
)}
</div>
{showScrollToBottom && <ScrollToBottom onClick={scrollToBottom} />}
{/* 因为是倒过来的,因此要前面的要放在后面 */}
{props.title && !props.hasMoreMessage && (
<ChatMessageHeader title={props.title} />

@ -0,0 +1,21 @@
import React from 'react';
import { Icon } from 'tailchat-design';
interface Props {
onClick: () => void;
}
/**
*
*/
export const ScrollToBottom: React.FC<Props> = React.memo((props) => {
return (
<div
className="absolute right-6 bottom-18 px-3 py-2 rounded-full bg-white dark:bg-black bg-opacity-50 shadow cursor-pointer z-10 w-11 h-11 flex justify-center items-center text-2xl hover:bg-opacity-80"
onClick={props.onClick}
>
<Icon icon="mdi:chevron-double-down" />
</div>
);
});
ScrollToBottom.displayName = 'ScrollToBottom';

@ -1,4 +1,4 @@
import React, { useMemo, useRef } from 'react';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { buildMessageItemRow } from './Item';
import type { MessageListProps } from './types';
import {
@ -11,6 +11,7 @@ import {
useMemoizedFn,
useSharedEventHandler,
} from 'tailchat-shared';
import { ScrollToBottom } from './ScrollToBottom';
const PREPEND_OFFSET = 10 ** 7;
@ -18,6 +19,11 @@ const virtuosoStyle: React.CSSProperties = {
height: '100%',
};
const overscan = {
main: 1000,
reverse: 1000,
};
/**
*
* : https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageList/VirtualizedMessageList.tsx
@ -25,16 +31,18 @@ const virtuosoStyle: React.CSSProperties = {
export const VirtualizedMessageList: React.FC<MessageListProps> = React.memo(
(props) => {
const listRef = useRef<VirtuosoHandle>(null);
const scrollerRef = useRef<HTMLElement>();
const numItemsPrepended = usePrependedMessagesCount(props.messages);
useSharedEventHandler('sendMessage', () => {
listRef.current?.scrollToIndex({
index: 'LAST',
align: 'end',
const scrollToBottom = useMemoizedFn(() => {
listRef.current?.scrollTo({
top: scrollerRef.current?.scrollHeight,
behavior: 'smooth',
});
});
useSharedEventHandler('sendMessage', scrollToBottom);
const handleLoadMore = useMemoizedFn(() => {
if (props.isLoadingMore) {
return;
@ -71,27 +79,38 @@ export const VirtualizedMessageList: React.FC<MessageListProps> = React.memo(
return buildMessageItemRow(props.messages, index);
});
const [showScrollToBottom, setShowScrollToBottom] = useState(false);
const atBottomStateChange = useMemoizedFn((atBottom: boolean) => {
if (atBottom) {
setShowScrollToBottom(false);
} else {
setShowScrollToBottom(true);
}
});
return (
<div className="flex-1">
<Virtuoso
style={virtuosoStyle}
ref={listRef}
scrollerRef={(ref) => (scrollerRef.current = ref as HTMLElement)}
firstItemIndex={PREPEND_OFFSET - numItemsPrepended}
initialTopMostItemIndex={Math.max(props.messages.length - 1, 0)}
computeItemKey={computeItemKey}
totalCount={props.messages.length}
overscan={{
main: 1000,
reverse: 1000,
}}
overscan={overscan}
itemContent={itemContent}
alignToBottom={true}
startReached={handleLoadMore}
atBottomStateChange={atBottomStateChange}
followOutput={followOutput}
defaultItemHeight={25}
atTopThreshold={100}
atBottomThreshold={40}
useWindowScroll={false}
/>
{showScrollToBottom && <ScrollToBottom onClick={scrollToBottom} />}
</div>
);
}

@ -46,7 +46,7 @@ const ChatBoxInner: React.FC<ChatBoxProps> = React.memo((props) => {
}
return (
<div className="w-full h-full flex flex-col select-text">
<div className="w-full h-full flex flex-col select-text relative">
<ChatMessageList
key={converseId}
title={converseTitle}

Loading…
Cancel
Save