All
PC硬件
经验记录
生活杂谈
日常踩坑
前端技术
测试
2022-10-12
经验记录
react
105
开发聊天室功能,聊天消息滚动默认从下往上的问题探讨

需求描述

开发一个在线聊天功能,打开会话时,消息内容容器固定高度,当消息列表数量超过内容容器高度时,出现滚动条,并且滚动条默认在最下方。

实现方案

1. 列表正常渲染,通过js控制滚动逻辑

这种方法最好理解和比较简单,通过调用dom的api让滚动条滚动到最底部即可。但是会存在一个小问题,当消息列表数据更新时,有概率滚动条会从顶部瞬间移动到底部,会造成一种像闪屏的感觉。 可以在切换会话时设置opacity为0,消息列表渲染完成后再设置回1来解决。

react例子

// 判断是否进行滚动到底部
const isReacthBottom = useRef(true);

useEffect(() => {
    isReacthBottom.current = true;
}, [sessionName]);

// 当列表数据发生改变时,手动让滚动条滚动到最下方
useEffect(() => {
    if (isReacthBottom.current) {
      wrapperRef.current!.scroll({ top: wrapperRef.current!.scrollHeight });
    }
}, [list]);

// 参考微信PC端,当前会话如果往上滚动了,有新消息来不进行滚动
useEffect(() => {
    const fn = () => {
      if (
        wrapperRef.current!.scrollTop + wrapperRef.current!.clientHeight ===
        wrapperRef.current!.scrollHeight
      ) {
        isReacthBottom.current = true;
      } else {
        isReacthBottom.current = false;
      }
    };
    wrapperRef.current!.addEventListener('scroll', fn);

    return () => {
      // eslint-disable-next-line react-hooks/exhaustive-deps
      wrapperRef.current!.removeEventListener('scroll', fn);
    };
}, []); 
  
  // 参考dom结构
  <div className="online-chat-content">
      <div className="header">{sessionName}</div>
      <div className="content" ref={wrapperRef}>
        {list.map((message, i, mapArray) => {
          const img = message.position === 'left' ? logo : avatar;
          const showTime =
            i === 0 || isTwoTimeDiffGt5Minutes(mapArray[i].time, mapArray[i - 1].time);

          return (
            <div key={message.id} className="content-item" style={{ opacity: showMsg ? 1 : 0 }}>
              {showTime && (
                <div className="content-time">
                  <span>{moment(message.time).format('YYYY-MM-DD HH:mm:ss')}</span>
                </div>
              )}
              <div
                className={cn(['content-message', message.position])}
              >
                <img width={30} src={img} alt="" />
                <div className="message">{message.content}</div>
              </div>
            </div>
          );
        })}
      </div>
    </div>

2. 通过flex-direction: column-reverse 解决

这种方法可以让滚动条默认从下往上,但是有浏览器兼容性问题,部分浏览器会出现content容器无法滚动的问题,并且还需要对渲染的列表数据进行reverse反转。

参考dom结构

<div class="content">
    <div class="message-item">1</div>
    <div class="message-item">2</div>
    <div class="message-item">3</div>
</div>

css代码

.content {
    display: flex;
    flex-direction: column-reverse;
    /** 
    * 为什么要使用max-height?
    * 因为当消息列表数据不足以形成滚动条时,消息会从下往上渲染,max-height可以完美解决
    */ 
    max-height: 500px;
    overflow-y: auto;
}
.message-item {
    height: 300px;
    flex-shrink: 0;
    flex-grow: 0;
}

3. 通过 transform 反转来解决 (目前我项目中使用的方法)

方法2的上位替代,主要使content容器进行翻转,然后使里面的message容器再翻转一次(回正)。

参考dom结构

<div class="content">
    <div class="message-item">1</div>
    <div class="message-item">2</div>
    <div class="message-item">3</div>
</div>

参考css代码

.content {
    max-height: 500px;
    overflow-y: auto;
    transform: scale(1, -1);
}
.message-item {
    height: 300px;
    transform: scale(1, -1);
}

此种方法可以很简单的内容从下往上渲染,但是需要将列表数据reverse反转,并且由于content容器反转了,导致滚动条滚动方向反过来了,需要通过几行js代码来解决。

参考js代码

const fn = (e) => {
  if (e.deltaY) {
    e.preventDefault();
    e.currentTarget.scrollTop -=
      parseFloat(getComputedStyle(e.currentTarget).getPropertyValue('font-size')) *
      (e.deltaY < 0 ? -1 : 1) *
      3;
  }
};
const content = document.querySelector('.content')
content.addEventListener('wheel', fn)
Back
© 2022 BBF Powered byNext.js&Prisma&Tailwind.css