英文:
Staying in one place when the new elements are added to the beginning of the div
问题
以下是翻译好的部分:
我正在编写一个聊天页面。在这里,我有一个 div 元素,它是消息容器。当用户向上滚动时,它会向服务器发送请求,接收旧消息并将它们添加到消息容器的开头。问题是,当消息被添加时,用户的滚动位置突然改变,并跳到另一个位置。
获取消息的函数:
this.$refs.messages.addEventListener("scroll", () => { // 给消息容器添加事件监听器
if (this.$refs.messages.scrollTop < 90) {
this.loadingGetMoreMessages = true
this.getMoreMessages() // 获取旧消息并将它们添加到所有消息数组中
}
})
Vue 结构:
<div class="d-flex flex-column gap-2 h-100 w-100 pt-3" style="overflow-y: auto; overflow-x: hidden"
ref="messages">
<div v-for="(message, index) in chat.messages" :key="index">
<div :class="{ 'message-container': true, 'flex-row-reverse': !message.yours }"
:ref="'m-' + message.message_id">
<div class="d-flex gap-2" :style="{ 'padding-right': !message.yours ? '20px' : '' }">
<div :class="{ 'your-message': message.yours, 'its-message': !message.yours }">
{{ message.message }}
<div class="d-flex align-items-center gap-2">
<i class="text-muted fi fi-br-check d-flex" v-if="!message.seen && message.yours"></i>
<i class="text-muted fi fi-br-check-double d-flex"
v-if="message.seen && message.yours"></i>
<p class="text-muted mt-1" style="font-size: .8rem"> {{ message.time }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
希望将新消息添加到 div 开头时不会更改用户的滚动位置。
英文:
I am writing a chat page. Here I have a div which is the message container. When the user scrolls up, it sends a request to the server and receives older messages and adds them to the beginning of the message container. The problem is that when messages are added, the scrolling location of the user suddenly changes and jumps to another place.
get messages function:
this.$refs.messages.addEventListener("scroll", () => { //add event listener to messages container
if (this.$refs.messages.scrollTop < 90) {
this.loadingGetMoreMessages = true
this.getMoreMessages() // get older messages and add them in all messages array
}
})
Vue structure:
<div class="d-flex flex-column gap-2 h-100 w-100 pt-3" style="overflow-y: auto; overflow-x: hidden"
ref="messages">
<div v-for="(message, index) in chat.messages" :key="index">
<div :class="{ 'message-container': true, 'flex-row-reverse': !message.yours }"
:ref="'m-' + message.message_id">
<div class="d-flex gap-2" :style="{ 'padding-right': !message.yours ? '20px' : '' }">
<div :class="{ 'your-message': message.yours, 'its-message': !message.yours }">
{{ message.message }}
<div class="d-flex align-items-center gap-2">
<i class="text-muted fi fi-br-check d-flex" v-if="!message.seen && message.yours"></i>
<i class="text-muted fi fi-br-check-double d-flex"
v-if="message.seen && message.yours"></i>
<p class="text-muted mt-1" style="font-size: .8rem"> {{ message.time }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
It is expected that adding new messages to the beginning of the div will not change the user's scroll position
答案1
得分: 1
保存滚动到容器底部的剩余距离。这是因为如果我们希望滚动被视为未更改并且在上面添加消息,那么底部滚动距离应保持不变:
const { scrollTop, scrollHeight } = this.$refs.messages;
const scrollBottom = scrollHeight - scrollTop;
然后,使用 this.$nextTick
等待DOM更新,将滚动的“底部”位置更改回保存的位置:
this.$nextTick(() => {
this.$refs.messages.scrollTop = this.$refs.messages.scrollHeight - scrollBottom;
});
示例代码:
<!-- begin snippet: js hide: false console: false babel: false -->
<!-- language: lang-js -->
const { createApp } = Vue;
const app = createApp({
data() {
return {
loadingGetMoreMessages: false,
chat: {
messages: Array(10)
.fill()
.map((_, i) => ({
message_id: i,
yours: false,
seen: false,
time: `09:${String(i).padStart(2, '0')}`,
message: `Foo ${i}`,
})),
},
};
},
mounted() {
this.$refs.messages.scrollTop = 91;
//add event listener to messages container
this.$refs.messages.addEventListener("scroll", () => {
if (this.$refs.messages.scrollTop < 90) {
this.loadingGetMoreMessages = true;
// get older messages and add them in all messages array
this.getMoreMessages()
}
})
},
methods: {
getMoreMessages() {
const { scrollTop, scrollHeight } = this.$refs.messages;
const scrollBottom = scrollHeight - scrollTop;
const { length } = this.chat.messages;
this.chat.messages = Array(5)
.fill()
.map((_, i) => ({
message_id: i + length,
yours: false,
seen: false,
time: `09:${String(i).padStart(2, '0')}`,
message: `Bar ${i}`,
}))
.concat(this.chat.messages);
this.$nextTick(() => {
this.$refs.messages.scrollTop = this.$refs.messages.scrollHeight - scrollBottom;
});
}
},
});
app.mount('#app');
<!-- language: lang-html -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.3.4/vue.global.min.js" integrity="sha512-Wbf9QOX8TxnLykSrNGmAc5mDntbpyXjOw9zgnKql3DgQ7Iyr5TCSPWpvpwDuo+jikYoSNMD9tRRH854VfPpL9A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css" integrity="sha512-t4GWSVZO1eC8BM339Xd7Uphw5s17a86tIZIj8qRxhnKub6WoyhnrxeCIMeAqBPgdZGlCcG2PrZjMc+Wr78+5Xg==" crossorigin="anonymous" referrerpolicy="no-referrer"
/>
<div id="app" style="height: 200px">
<div class="d-flex flex-column gap-2 h-100 w-100 pt-3" style="overflow-y: auto; overflow-x: hidden" ref="messages">
<div v-for="(message, index) in chat.messages" :key="index">
<div :class="{ 'message-container': true, 'flex-row-reverse': !message.yours }" :ref="'m-' + message.message_id">
<div class="d-flex gap-2" :style="{ 'padding-right': !message.yours ? '20px' : '' }">
<div :class="{ 'your-message': message.yours, 'its-message': !message.yours }">
{{ message.message }}
<div class="d-flex align-items-center gap-2">
<i class="text-muted fi fi-br-check d-flex" v-if="!message.seen && message.yours"></i>
<i class="text-muted fi fi-br-check-double d-flex" v-if="message.seen && message.yours"></i>
<p class="text-muted mt-1" style="font-size: .8rem"> {{ message.time }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- end snippet -->
英文:
Save the remaining distance to scroll to the bottom of the container. This is because if we want the scroll to be perceived as unchanged and we add messages above, then this bottom scroll distance should remain unchanged:
const { scrollTop, scrollHeight } = this.$refs.messages;
const scrollBottom = scrollHeight - scrollTop;
Then, use this.$nextTick
to wait for the DOM update from prepending the messages to change the scroll "bottom" position back to what we saved before:
this.$nextTick(() => {
this.$refs.messages.scrollTop = this.$refs.messages.scrollHeight - scrollBottom;
});
Live example:
<!-- begin snippet: js hide: false console: false babel: false -->
<!-- language: lang-js -->
const { createApp } = Vue;
const app = createApp({
data() {
return {
loadingGetMoreMessages: false,
chat: {
messages: Array(10)
.fill()
.map((_, i) => ({
message_id: i,
yours: false,
seen: false,
time: `09:${String(i).padStart(2, '0')}`,
message: `Foo ${i}`,
})),
},
};
},
mounted() {
this.$refs.messages.scrollTop = 91;
//add event listener to messages container
this.$refs.messages.addEventListener("scroll", () => {
if (this.$refs.messages.scrollTop < 90) {
this.loadingGetMoreMessages = true;
// get older messages and add them in all messages array
this.getMoreMessages()
}
})
},
methods: {
getMoreMessages() {
const { scrollTop, scrollHeight } = this.$refs.messages;
const scrollBottom = scrollHeight - scrollTop;
const { length } = this.chat.messages;
this.chat.messages = Array(5)
.fill()
.map((_, i) => ({
message_id: i + length,
yours: false,
seen: false,
time: `09:${String(i).padStart(2, '0')}`,
message: `Bar ${i}`,
}))
.concat(this.chat.messages);
this.$nextTick(() => {
this.$refs.messages.scrollTop = this.$refs.messages.scrollHeight - scrollBottom;
});
}
},
});
app.mount('#app');
<!-- language: lang-html -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.3.4/vue.global.min.js" integrity="sha512-Wbf9QOX8TxnLykSrNGmAc5mDntbpyXjOw9zgnKql3DgQ7Iyr5TCSPWpvpwDuo+jikYoSNMD9tRRH854VfPpL9A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css" integrity="sha512-t4GWSVZO1eC8BM339Xd7Uphw5s17a86tIZIj8qRxhnKub6WoyhnrxeCIMeAqBPgdZGlCcG2PrZjMc+Wr78+5Xg==" crossorigin="anonymous" referrerpolicy="no-referrer"
/>
<div id="app" style="height: 200px">
<div class="d-flex flex-column gap-2 h-100 w-100 pt-3" style="overflow-y: auto; overflow-x: hidden" ref="messages">
<div v-for="(message, index) in chat.messages" :key="index">
<div :class="{ 'message-container': true, 'flex-row-reverse': !message.yours }" :ref="'m-' + message.message_id">
<div class="d-flex gap-2" :style="{ 'padding-right': !message.yours ? '20px' : '' }">
<div :class="{ 'your-message': message.yours, 'its-message': !message.yours }">
{{ message.message }}
<div class="d-flex align-items-center gap-2">
<i class="text-muted fi fi-br-check d-flex" v-if="!message.seen && message.yours"></i>
<i class="text-muted fi fi-br-check-double d-flex" v-if="message.seen && message.yours"></i>
<p class="text-muted mt-1" style="font-size: .8rem"> {{ message.time }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- end snippet -->
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论