【问题标题】:How to scroll up history in React chat page如何在 React 聊天页面中向上滚动历史记录
【发布时间】:2021-12-30 17:05:43
【问题描述】:

*试图以无限重新加载的方式显示聊天记录,类似于 Skype 或任何流行的聊天应用程序

在聊天页面中。如果我的聊天消息限制为 10 条消息。

聊天有 30 个。

加载聊天时会显示最新的 10 个。

当我滚动到顶部时,我想查看前 10 个。

我自己尝试过,滚动位置保持不变,但消息会加载到视图中。

它应该加载到顶部并保持滚动位置。

如何做到这一点?

这是我的页面: https://pastebin.com/36xZPG1W

import React, { useRef, useState, useEffect } from 'react';
import produce from 'immer';
import dayjs from 'dayjs';
import { WithT } from 'i18next';
 
import * as ErrorHandler from 'components/ErrorHandler';
import useOnScreen from 'utils/useOnScreen';
 
import getLang from 'utils/getLang';
 
import Message from './Message';
const limit = 10;
const lang = getLang();
interface IMessagesProps extends WithT {
  messages: any;
  currentUserID: string;
  chatID: string;
  fetchMore: any;
  typingText: any;
  setSelectedMsg: any;
  removeMessage: any;
}
 
const Messages: React.FC<IMessagesProps> = ({
  messages,
  currentUserID,
  chatID,
  fetchMore,
  setSelectedMsg,
  removeMessage,
  t,
}) => {
  const elementRef = useRef(null);
  const isOnScreen = useOnScreen(elementRef);
  const topElementRef = useRef(null);
  const topIsOnScreen = useOnScreen(topElementRef);
 
  const isUserInside = useRef(true);
  const scroller = useRef<HTMLDivElement>(null);
  const messagesEnd = useRef<HTMLDivElement>(null);
  const [hasMore, setHasMore] = useState(true);
 
  useEffect(() => {
    scrollToBottom();
  }, []);
 
  useEffect(() => {
    autoscroll();
  }, [messages]);
 
  //NOT WORKING
  const autoscroll = () => {
    // Visible height
    const visibleHeight = scroller.current.offsetHeight;
 
    // Height of messages container
    const containerHeight = scroller.current.scrollHeight;
 
    // How far have I scrolled?
    const scrollOffset = scroller.current.scrollTop + visibleHeight;
    // New message element
    const firstChild = scroller.current.firstElementChild;
    console.log(`visibleHeight`, visibleHeight);
    console.log(`containerHeight`, containerHeight);
    console.log(`scrollOffset`, scrollOffset);
    console.log(`firstChild`, firstChild.offsetHeight);
    console.log(`firstChild`, firstChild.scrollHeight);
    console.log(`firstChild`, firstChild);
    scroller.current.scrollTop = scrollOffset;
    // // Height of the new message
    // const newMessageStyles = getComputedStyle($newMessage)
    // const newMessageMargin = parseInt(newMessageStyles.marginBottom)
    // const newMessageHeight = $newMessage.offsetHeight + newMessageMargin
 
    // // Visible height
    // const visibleHeight = $messages.offsetHeight
 
    // // Height of messages container
    // const containerHeight = $messages.scrollHeight
 
    // // How far have I scrolled?
    // const scrollOffset = $messages.scrollTop + visibleHeight
 
    // if (containerHeight - newMessageHeight <= scrollOffset) {
    //     $messages.scrollTop = $messages.scrollHeight
    // }
  };
 
  const fetchDataForScrollUp = cursor => {
    ErrorHandler.setBreadcrumb('fetch more messages');
    if (!hasMore) {
      return;
    }
    fetchMore({
      variables: {
        chatID,
        limit,
        cursor,
      },
      updateQuery: (previousResult, { fetchMoreResult }) => {
        if (!fetchMoreResult?.getMessages || fetchMoreResult.getMessages.messages.length < limit) {
          setHasMore(false);
          return previousResult;
        }
        const newData = produce(previousResult, draftState => {
          draftState.getMessages.messages = [...previousResult.getMessages.messages, ...fetchMoreResult.getMessages.messages];
        });
 
        return newData;
      },
    });
  };
 
  if (messages?.length >= limit) {
    if (topIsOnScreen) {
      fetchDataForScrollUp(messages[messages.length - 1].id);
    }
  }
 
  if (isOnScreen) {
    isUserInside.current = true;
  } else {
    isUserInside.current = false;
  }
 
  const scrollToBottom = () => {
    if (messagesEnd.current) {
      messagesEnd.current.scrollIntoView({ behavior: 'smooth' });
    }
  };
 
  const groupBy = function (arr, criteria) {
    return arr.reduce(function (obj, item) {
      // Check if the criteria is a function to run on the item or a property of it
      const key = typeof criteria === 'function' ? criteria(item) : item[criteria];
 
      // If the key doesn't exist yet, create it
      if (!Object.prototype.hasOwnProperty.call(obj, key)) {
        obj[key] = [];
      }
 
      // Push the value to the object
      obj[key].push(item);
 
      // Return the object to the next item in the loop
      return obj;
    }, {});
  };
 
  const objectMap = object => {
    return Object.keys(object).reduce(function (result, key) {
      result.push({ date: key, messages: object[key] });
      return result;
    }, []);
  };
 
  const group = groupBy(messages, datum => dayjs(datum.createdAt).locale(lang).format('dddd, MMMM D, YYYY').toLocaleUpperCase());
  const messageElements = objectMap(group)
    .reverse()
    .map((item, index) => {
      const messageElements = item.messages
        .map(message => {
              return (
                <Message
                  key={uniqueKey}
                  message={message}
                  currentUserID={currentUserID}
                  lang={lang}
                  removeMessage={removeMessage}
                  t={t}
                  chatID={chatID}
                  setSelectedMsg={setSelectedMsg}
                />
              );
        })
        .reverse();
 
      return messageElements;
    })
    .reduce((a, b) => a.concat(b), []);
 
  return (
    <div style={{ marginBottom: '25px' }}>
      <div ref={topElementRef} />
      <div
        style={{
          position: 'relative',
          display: 'flex',
          flexDirection: 'column',
          flexWrap: 'wrap',
          height: '100%',
          overflow: 'hidden',
        }}
        ref={scroller}
      >
        {messageElements}
        <div ref={elementRef} style={{ position: 'absolute', bottom: '5%' }} />
      </div>
    </div>
  );
};
 
export default Messages;

在这个问题上卡住了 2 周,哈哈。任何建议都有帮助:)

【问题讨论】:

    标签: reactjs react-native react-redux react-hooks jquery-waypoints


    【解决方案1】:

    你试过scrollIntoView 吗?您可以在更改 autoscroll 函数后尝试,如下所示

    const autoscroll = () => {
       elementRef.current.scrollIntoView({ behavior: 'smooth' })
      };
    

    【讨论】:

    • 我怎么知道滚动到哪个元素?
    • 更好的是,在请求新元素时如何/在哪里维护对顶部元素的引用?
    • 你可以有条件地在旧消息和新消息之间添加一个带有ref的div,一旦你到达新消息,删除添加的div并将新消息合并到旧消息中,并清除新的消息值
    • 啊,这就是问题所在。当您合并新消息时,会重新呈现。顶部 div 将简单地重置到顶部,并且不会从顶部移动。
    • 我能够接近这个。但它仍然达到顶峰。然后重新渲染并滚动到位置。它不是我们期望的无限滚动代码: const autoscroll = () => { if(scroller.current?.children[limit+1]){ scroller.current?.children[limit+1].scrollIntoView({behavior :"顺利"});} };
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-10-25
    • 1970-01-01
    • 2012-09-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-24
    • 1970-01-01
    相关资源
    最近更新 更多