[Vuejs]-Staying in one place when the new elements are added to the beginning of the div

1👍

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:

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');
<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>
👤Wongjn

Leave a comment