# 前置
- 我们都知道,聊天记录是滚动的,所以,我们需要知道滚动条的位置,然后,通过滚动条的位置,来定位到聊天记录的位置。
- 聊天记录的
flex-direction
是column-reverse
的,所以最底下的container.scrollTop
为 0,上滑动减少 getBoundingClientRect
可以获取到元素的位置,包括top
、bottom
、left
、right
等。这些都是相对于视口的位置。
# 代码
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<title>Scroll to Message Example</title> | |
<style> | |
body { | |
margin: 0; | |
font-family: Arial, sans-serif; | |
} | |
#message-container { | |
display: flex; | |
flex-direction: column-reverse; /* 反向排列 */ | |
height: 400px; /* 固定高度 */ | |
overflow-y: auto; /* 垂直滚动 */ | |
border: 1px solid #ccc; | |
padding: 16px; | |
box-sizing: border-box; | |
} | |
.message { | |
background-color: #f0f0f0; | |
padding: 8px 12px; | |
margin-bottom: 8px; | |
border-radius: 4px; | |
} | |
.highlight-message { | |
background-color: yellow !important; /* 高亮样式 */ | |
} | |
</style> | |
</head> | |
<body> | |
<h1>Scroll to Message Example</h1> | |
<div id="message-container"> | |
<!-- 消息列表 --> | |
<div class="message" data-message-id="1">Message 1 (Oldest)</div> | |
<div class="message" data-message-id="2">Message 2</div> | |
<div class="message" data-message-id="3">Message 3</div> | |
<div class="message" data-message-id="4">Message 4</div> | |
<div class="message" data-message-id="5">Message 5</div> | |
<div class="message" data-message-id="6">Message 6</div> | |
<div class="message" data-message-id="7">Message 7</div> | |
<div class="message" data-message-id="8">Message 8</div> | |
<div class="message" data-message-id="9">Message 9</div> | |
<div class="message" data-message-id="10">Message 10 (Newest)</div> | |
<div class="message" data-message-id="11">Message 11</div> | |
<div class="message" data-message-id="12">Message 12</div> | |
<div class="message" data-message-id="13">Message 13</div> | |
<div class="message" data-message-id="14">Message 14</div> | |
<div class="message" data-message-id="15">Message 15</div> | |
<div class="message" data-message-id="16">Message 16</div> | |
<div class="message" data-message-id="17">Message 17</div> | |
<div class="message" data-message-id="18">Message 18</div> | |
</div> | |
<button onclick="scrollToMessage(18)">Scroll to Message 18</button> | |
<script> | |
function scrollToMessage(messageId) { | |
const container = document.getElementById("message-container"); | |
const targetElement = container.querySelector( | |
`[data-message-id="${messageId}"]` | |
); | |
if (targetElement) { | |
// 获取容器和目标元素的位置信息 | |
const containerRect = container.getBoundingClientRect(); // 文本矩形相对于视口的信息 | |
const elementRect = targetElement.getBoundingClientRect(); // 目标元素相对于视口的矩形信息 | |
console.log(containerRect.top, elementRect.top); | |
// 计算需要滚动的距离(考虑反向滚动) | |
const scrollOffset = elementRect.top - containerRect.top; | |
const containerCenter = containerRect.height / 2; | |
console.log(containerRect.height); | |
console.log(container.scrollTop); | |
const targetScrollTop = | |
container.scrollTop + scrollOffset - containerCenter; | |
// 执行平滑滚动 | |
container.scrollTo({ | |
top: targetScrollTop, | |
behavior: "smooth", | |
}); | |
// 高亮逻辑 | |
targetElement.classList.add("highlight-message"); | |
setTimeout( | |
() => targetElement.classList.remove("highlight-message"), | |
2000 | |
); | |
} else { | |
console.error(`Message with ID ${messageId} not found.`); | |
} | |
} | |
</script> | |
</body> | |
</html> |
# 图解
[目标消息] target
+-----------------------------+
| 视口顶部 |
| |
| [旧消息] <-- 容器顶部 container |
| [消息2] |
| [消息3] |
| ... |
| [目标消息] target |
| ... |
| [新消息] <-- 容器底部 |
| |
| 视口底部 |
+-----------------------------+
这里的 target
和 container
是容器和消息相对于视口顶部的距离,也就是 container.getBoundingClientRect();
和 targetElement.getBoundingClientRect();
的 top
值。
用目标的值减去容器的值,就是需要滚动的距离。container.scrollTop + target - container
就可以得到把目标元素滚进容器内的滚动条的位置。
但是我们需要放在中间,所以,需要减去容器的中间位置。也就是减去 containerRect.height / 2;
当然,目标消息在视口上方时, getBoundingClientRect
的 top
值是负数,操作也是一样的。
由此可得,这个解具有普遍性