在新元素添加到 `div` 开头时保持在同一位置。

huangapple go评论72阅读模式
英文:

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(&quot;scroll&quot;, () =&gt; { //add event listener to messages container
    if (this.$refs.messages.scrollTop &lt; 90) {
        this.loadingGetMoreMessages = true 
        this.getMoreMessages() // get older messages and add them in all messages array
    }
})

Vue structure:

&lt;div class=&quot;d-flex flex-column gap-2 h-100 w-100 pt-3&quot; style=&quot;overflow-y: auto; overflow-x: hidden&quot;
ref=&quot;messages&quot;&gt;
    &lt;div v-for=&quot;(message, index) in chat.messages&quot; :key=&quot;index&quot;&gt;
        &lt;div :class=&quot;{ &#39;message-container&#39;: true, &#39;flex-row-reverse&#39;: !message.yours }&quot;
            :ref=&quot;&#39;m-&#39; + message.message_id&quot;&gt;
            &lt;div class=&quot;d-flex gap-2&quot; :style=&quot;{ &#39;padding-right&#39;: !message.yours ? &#39;20px&#39; : &#39;&#39; }&quot;&gt;
                &lt;div :class=&quot;{ &#39;your-message&#39;: message.yours, &#39;its-message&#39;: !message.yours }&quot;&gt;
                    {{ message.message }}
                    &lt;div class=&quot;d-flex align-items-center gap-2&quot;&gt;
                        &lt;i class=&quot;text-muted fi fi-br-check d-flex&quot; v-if=&quot;!message.seen &amp;&amp; message.yours&quot;&gt;&lt;/i&gt;
                        &lt;i class=&quot;text-muted fi fi-br-check-double d-flex&quot;
                            v-if=&quot;message.seen &amp;&amp; message.yours&quot;&gt;&lt;/i&gt;
                        &lt;p class=&quot;text-muted mt-1&quot; style=&quot;font-size: .8rem&quot;&gt; {{ message.time }}&lt;/p&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

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(() =&gt; {
  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) =&gt; ({
message_id: i,
yours: false,
seen: false,
time: `09:${String(i).padStart(2, &#39;0&#39;)}`,
message: `Foo ${i}`,
})),
},
};
},
mounted() {
this.$refs.messages.scrollTop = 91;
//add event listener to messages container
this.$refs.messages.addEventListener(&quot;scroll&quot;, () =&gt; {
if (this.$refs.messages.scrollTop &lt; 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) =&gt; ({
message_id: i + length,
yours: false,
seen: false,
time: `09:${String(i).padStart(2, &#39;0&#39;)}`,
message: `Bar ${i}`,
}))
.concat(this.chat.messages);
this.$nextTick(() =&gt; {
this.$refs.messages.scrollTop = this.$refs.messages.scrollHeight - scrollBottom;
});
}
},
});
app.mount(&#39;#app&#39;);

<!-- language: lang-html -->

&lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/vue/3.3.4/vue.global.min.js&quot; integrity=&quot;sha512-Wbf9QOX8TxnLykSrNGmAc5mDntbpyXjOw9zgnKql3DgQ7Iyr5TCSPWpvpwDuo+jikYoSNMD9tRRH854VfPpL9A==&quot; crossorigin=&quot;anonymous&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/script&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css&quot; integrity=&quot;sha512-t4GWSVZO1eC8BM339Xd7Uphw5s17a86tIZIj8qRxhnKub6WoyhnrxeCIMeAqBPgdZGlCcG2PrZjMc+Wr78+5Xg==&quot; crossorigin=&quot;anonymous&quot; referrerpolicy=&quot;no-referrer&quot;
/&gt;
&lt;div id=&quot;app&quot; style=&quot;height: 200px&quot;&gt;
&lt;div class=&quot;d-flex flex-column gap-2 h-100 w-100 pt-3&quot; style=&quot;overflow-y: auto; overflow-x: hidden&quot; ref=&quot;messages&quot;&gt;
&lt;div v-for=&quot;(message, index) in chat.messages&quot; :key=&quot;index&quot;&gt;
&lt;div :class=&quot;{ &#39;message-container&#39;: true, &#39;flex-row-reverse&#39;: !message.yours }&quot; :ref=&quot;&#39;m-&#39; + message.message_id&quot;&gt;
&lt;div class=&quot;d-flex gap-2&quot; :style=&quot;{ &#39;padding-right&#39;: !message.yours ? &#39;20px&#39; : &#39;&#39; }&quot;&gt;
&lt;div :class=&quot;{ &#39;your-message&#39;: message.yours, &#39;its-message&#39;: !message.yours }&quot;&gt;
{{ message.message }}
&lt;div class=&quot;d-flex align-items-center gap-2&quot;&gt;
&lt;i class=&quot;text-muted fi fi-br-check d-flex&quot; v-if=&quot;!message.seen &amp;&amp; message.yours&quot;&gt;&lt;/i&gt;
&lt;i class=&quot;text-muted fi fi-br-check-double d-flex&quot; v-if=&quot;message.seen &amp;&amp; message.yours&quot;&gt;&lt;/i&gt;
&lt;p class=&quot;text-muted mt-1&quot; style=&quot;font-size: .8rem&quot;&gt; {{ message.time }}&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;

<!-- end snippet -->

huangapple
  • 本文由 发表于 2023年6月27日 21:04:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/76565175.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定