diff --git a/package.json b/package.json index 064a4b4..ddcef57 100644 --- a/package.json +++ b/package.json @@ -15,14 +15,14 @@ "scripts": { "clean": "rm -rf dist && rm -rf es && rm -rf lib", "prebuild": "npm run clean", - "prefix": "NODE_ENV=production postcss dist/index.css -o dist/index.css", + "prefix": "cross-env NODE_ENV=production postcss dist/index.css -o dist/index.css", "copy:less": "copyfiles -u 1 src/**/*.less src/**/**/*.less es", - "js:cjs": "BABEL_ENV=cjs babel src -d lib --extensions '.ts,.tsx'", - "js:esm": "BABEL_ENV=esm babel src -d es --extensions '.ts,.tsx'", + "js:cjs": "cross-env BABEL_ENV=cjs babel src -d lib --extensions '.ts,.tsx'", + "js:esm": "cross-env BABEL_ENV=esm babel src -d es --extensions '.ts,.tsx'", "build:types": "tsc -p tsconfig.build.json", "build": "npm run js:cjs && npm run js:esm && npm run build:types", "build:css": "lessc src/styles/index.less dist/index.css && npm run prefix && npm run copy:less", - "build:umd": "BABEL_ENV=umd rollup -c && npm run build:css", + "build:umd": "cross-env BABEL_ENV=umd rollup -c && npm run build:css", "prepublishOnly": "npm run build && npm run build:umd", "test": "jest", "test:watch": "jest --watch", @@ -62,6 +62,7 @@ "@typescript-eslint/parser": "^5.60.1", "autoprefixer": "^10.4.14", "copyfiles": "^2.4.1", + "cross-env": "^10.0.0", "cssnano": "^6.0.1", "eslint": "^8.44.0", "eslint-config-airbnb": "^19.0.4", diff --git a/src/components/Chat/index.tsx b/src/components/Chat/index.tsx index 0fe569e..334df5b 100644 --- a/src/components/Chat/index.tsx +++ b/src/components/Chat/index.tsx @@ -178,6 +178,7 @@ export const Chat = React.forwardRef((props, ref) => rightAction, Composer = DComposer, isX, + scrollBehaviorConfig, } = props; const [currentColorScheme, setCurrentColorScheme] = useState<'light' | 'dark'>('light'); @@ -252,6 +253,7 @@ export const Chat = React.forwardRef((props, ref) => onScroll={onScroll} onBackBottomShow={onBackBottomShow} onBackBottomClick={onBackBottomClick} + scrollBehaviorConfig={scrollBehaviorConfig} />
{renderQuickReplies ? ( diff --git a/src/components/MessageContainer/index.tsx b/src/components/MessageContainer/index.tsx index ad6bda6..4019d77 100644 --- a/src/components/MessageContainer/index.tsx +++ b/src/components/MessageContainer/index.tsx @@ -9,6 +9,19 @@ import getToBottom from '../../utils/getToBottom'; const listenerOpts = canUse('passiveListener') ? { passive: true } : false; +const DEFAULT_FOLLOW_SCREEN = 2; + +export interface ScrollBehaviorConfig { + /** 是否跟随自己发的消息,默认 true */ + followSelf?: boolean; + /** 是否跟随收到的消息,默认 true */ + followIncoming?: boolean; + /** 不在底部时是否显示新消息数量,默认 true */ + showNewCount?: boolean; + /** 距离多少屏以内才自动跟随,默认 2 */ + followScreen?: number; +} + export interface MessageContainerProps { messages: MessageProps[]; renderMessageContent: (message: MessageProps) => React.ReactNode; @@ -19,6 +32,8 @@ export interface MessageContainerProps { renderBeforeMessageList?: () => React.ReactNode; onBackBottomShow?: () => void; onBackBottomClick?: () => void; + /** 滚动行为配置 */ + scrollBehaviorConfig?: ScrollBehaviorConfig; } export interface MessageContainerHandle { @@ -43,6 +58,7 @@ export const MessageContainer = React.forwardRef(null); const scrollerRef = useRef(null); const lastMessage = messages[messages.length - 1]; + const scrollBehaviorConfigRef = useRef(scrollBehaviorConfig); const clearBackBottom = () => { setNewCount(0); @@ -115,23 +132,45 @@ export const MessageContainer = React.forwardRef { + scrollBehaviorConfigRef.current = scrollBehaviorConfig; + }, [scrollBehaviorConfig]); + useEffect(() => { const scroller = scrollerRef.current; const wrapper = scroller && scroller.wrapperRef.current; - + if (!wrapper || !lastMessage || lastMessage.position === 'pop') { return; } + const { + followSelf = true, + followIncoming = true, + followScreen = DEFAULT_FOLLOW_SCREEN, + showNewCount = true, + } = scrollBehaviorConfigRef.current; + + if (lastMessage.position === 'right') { - // 自己发的消息,强制滚动到底部 - scrollToEnd({ force: true }); - } else if (isNearBottom(wrapper, 2)) { - const animated = !!wrapper.scrollTop; - scrollToEnd({ animated, force: true }); + // 自己发的消息 + if (followSelf) { + scrollToEnd({ force: true }); + } } else { - setNewCount((c) => c + 1); - setShowBackBottom(true); + // 收到的消息 + if (followIncoming && isNearBottom(wrapper, followScreen)) { + const animated = !!wrapper.scrollTop; + scrollToEnd({ animated, force: true }); + } else { + // 防止滚动条较底下的时候出现回到底部的按钮 + if (!isNearBottom(wrapper, Math.max(followScreen, DEFAULT_FOLLOW_SCREEN))) { + if (showNewCount) { + setNewCount((c) => c + 1); + } + setShowBackBottom(true); + } + } } }, [lastMessage, scrollToEnd]);