开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天,点击查看活动详情
背景
笔者最近在参与一个客服咨询系统
的开发工作,负责的是客服端
的 WEB
平台,该平台面向于公司内部的客服工作人员,为客服
提供和客户
在线沟通的功能,目前只做了文字消息
,后续会考虑加上语音
、图片
、视频
等沟通媒介,双方的消息
都放到一个列表中,再渲染到 DOM
上。
开发过程中遇到一个问题,在 DOM
文档中,消息
过多使得消息列表的高度
溢出父元素后,父元素产生滚动条,溢出的消息
需要下拉滚动条才能看到,这非常影响用户体验。
于是,笔者开始调研聊天窗口中消息自动滚动到底部
的解决方案,并确定了一种方案,实现了该功能,最后输出了本文。下面是笔者的心路历程以及方案的详细描述,掘友们如果有更好的方案,欢迎指教👏👏
实现功能
实现本功能的核心方法是 Element.scrollIntoView,同时还涉及CSS盒模型
,CSS伪类
,获取DOM结点
等知识。
获取最后一条消息的 DOM 结点
笔者采用 Element.querySelector
来获取 DOM
结点,因为此方法可通过 CSS选择器
的方式来使用,通过 :last-of-type
就可以轻松选到最后一个消息结点,笔者觉得很方便。
const lastMessage = document.querySelector('.message:last-of-type');
滚动消息的父元素
父元素已设置 overflow:auto
,因此当消息溢出
时已经出现滚动条,此时调用消息结点
的 scrollIntoView
方法,该方法滚动的是 element.parent
,正好是消息结点的父元素。
Element.scrollIntoView
方法接收一个参数,该参数可以对滚动过程进行定制,笔者使用Object型参数
传参,执行的动画效果是 smooth
过渡,垂直方向对齐为 end
,水平方向对齐为 nearest
。
lastMessage.scrollIntoView({ behavior: "smooth", block: "end", inline: "nearest" });
出现问题
此时问题出现,.message
的父元素不能滚动到底部,这是因为笔者给 .message
这个盒子设置了 margin-bottom
,而 Element.scrollIntoView
只会将父元素
滚动到 完整出现 message 的 Border Box 区域
,不包括 Margin Box
部分 (参考CSS盒模型),所以最后滚动完毕时会缺少
滚动一部分 margin-bottom
的距离,看起来就是没有滚动到底。
解决问题
笔者改变了 .message
的样式,当然不影响 UI
的展示,只是把 .message
的 margin-bottom
改成了 padding-bottom
。
因为在 CSS盒模型
中, Padding Box
在 Border Box
的内侧,所以当完整出现 Border Box
时,Padding Box
必然已经出现了,这样就能把 .message
盒子完整地展示出来,视觉上就是滚动到底
的效果。
DEMO
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>聊天窗口消息自动滚动到底部</title>
<style>
.chat {
display: flex;
align-items: center;
justify-content: center;
height: 600px;
}
.chat-body {
height: 400px;
width: fit-content;
overflow: auto;
border: 1px solid yellowgreen;
}
.message {
width: 500px;
height: 80px;
/* margin-top: 10px; */
/* margin-bottom: 10px; */
padding-top: 10px;
padding-bottom: 10px;
background-color: #eee;
}
</style>
</head>
<body>
<div>
<button onclick="doit()">滚动到底部</button>
</div>
<div class="chat">
<div class="chat-body">
<div class="message">message1</div>
<div class="message">message2</div>
<div class="message">message3</div>
<div class="message">message4</div>
<div class="message">message5</div>
<div class="message">message6</div>
</div>
</div>
<script>
function doit() {
const lastMessage = document.querySelector('.message:last-of-type');
lastMessage.scrollIntoView({ behavior: "smooth", block: "end", inline: "nearest" });
}
</script>
</body>
</html>