开发一个在线聊天功能,打开会话时,消息内容容器固定高度,当消息列表数量超过内容容器高度时,出现滚动条,并且滚动条默认在最下方。
这种方法最好理解和比较简单,通过调用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>
这种方法可以让滚动条默认从下往上,但是有浏览器兼容性问题,部分浏览器会出现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;
}
方法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)