Remaining stationary as additional elements are inserted at the start of the div

I'm currently working on a chat page. Within this page, there is a div element that serves as the message container. As users scroll up, a request is sent to the server to retrieve older messages, which are then added to the beginning of the message container. However, an issue arises where the user's scrolling position suddenly changes when new messages are added. Here is the function for getting messages:

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>

The goal is to ensure that adding new messages at the beginning of the div does not disrupt the user's scroll position

Answer №1

Remember to save the remaining distance needed to scroll to the bottom of the container. This is crucial for maintaining the perceived unchanged scroll position, especially when new messages are added above:

const { scrollTop, scrollHeight } = this.$refs.messages;
const scrollBottom = scrollHeight - scrollTop;

Afterwards, utilize this.$nextTick to wait for the DOM update after prepending the messages and revert the scroll "bottom" position back to its previously saved value:

this.$nextTick(() => {
  this.$refs.messages.scrollTop = this.$refs.messages.scrollHeight - scrollBottom;
});

Check out this live example below:

// Live Vue.js 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;
        // Fetch older messages and add them to the existing 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>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Discovering memory leaks in node.js/javascript algorithms

I am currently dealing with a piece of code that is functioning as expected, except for the fact that it contains a memory leak. Are there any effective methods for detecting memory leaks in node.js? What procedures should I undertake to identify and res ...

Using VueJS: Automatically scroll to a specific line in a table when the page loads

In my element framework table, I have a list of questions. I can select one to edit, make changes, and after validating, I want the page to automatically scroll to the modified question. <template> <div> <el-table :data="lis ...

Guide on how to choose a radio button in IONIC with the help of angularJS

<ion-list> <ion-radio ng-model="choice" ng-value="'A'" ng-click='usedGNG("yes")'>Yes</ion-radio> <ion-radio ng-model="choice" ng-value="'B'" ng-click='usedGNG("no")'>No</ion-radio> </ ...

MVC3 does not support JQuery UI

Just recently, I had all my jquery-ui elements functioning perfectly. However, now it seems that none of the jquery-ui features are working anymore. In my Site.Master page, I have included the following code... <link href="../../Content/Site.css" rel=" ...

The onclick function in the Navbar div fails to work for inner elements

Within my navbar, there is a .dropbtn div that I want to trigger a dropdown function when clicked. However, only the text "TOTAL" seems to activate this function onclick. The <span> and <i> elements inside the .dropbtn do not respond to clicks ...

What is the reason behind Chrome removing an SVG image pattern while utilizing jQuery Draggable?

Currently, I am trying to encapsulate an SVG within a draggable div. The SVG contains a shape or path with an image fill on the face. Surprisingly, it displays perfectly and functions flawlessly in Firefox. However, when it comes to Chrome, the dragging op ...

I'm experiencing a connection issue despite using the same call file. Why could this be happening?

Here is my situation: I have developed a WebSocket Server using Node.js within an environment primarily based on Laravel. To mimic the functionality of Laravel's .env file, I have incorporated the dotenv package. Recently, I came across a strange obs ...

Tips for passing parameters in the $http GET request to transmit information from a dropdown menu using AngularJS

In the modal window I have for creating a new object, there are three forms: 1) a simple input to create "Name"; 2) A dropdown with "Types"; 3) A dropdown with "Ids". The issue arises when trying to send the data after filling out all the forms. An error o ...

Deactivate the collapse toggle feature in the footer navigation

I've recently started using bootstrap and I'm having some trouble adding a social nav footer to my portfolio. I've experimented with removing different classes and even tried using the sticky footer feature, but it's not quite what I ne ...

Is there a way to alter the timestamp on comments in a React Native app, similar to Instagram's functionality, using dayjs?

I am currently working on a project using react native in combination with dayjs library. My goal is to compare the timestamp of when a comment was written with the current time, and then display this compared time using console.log() with the help of day ...

Change the classes of the body prior to the initial rendering

I know this may seem like a difficult task, and I understand that what I want to achieve might be nearly impossible. My goal is to incorporate a dark/light mode switch on my website. The challenge lies in the fact that the site consists of static files on ...

Tracking page views through ajax requests on Google Analytics

I have implemented a method to log page views through ajax action when the inner page content is loaded. However, I am facing an issue where the bounce rate data is not getting updated and always shows 0%. The default Google Analytics page view is logged ...

Using Vue routing results in the page not correctly loading (fails to function)

Struggling with routing in my JS project. Contact me at [email protected], using vue version 3.5.5 and springboot for the backend. I've incorporated 2 plugins in pom.xml to run both the backend and frontend on the same port locally. However, when ...

Twice Asynchronous AJAX Action

Currently, I am working on a javascript script to dynamically load pages on my website without the need to refresh the entire page. The script involves fading out the content div, loading new content, switching the content, and then fading it back in. The ...

The TypeScript error message reads: "You cannot assign type 'undefined' to type 'ReactElement'."

I'm encountering an error in my react project TypeScript error Argument of type 'ReactNode' is not compatible with parameter of type 'ReactElement<any, string | JSXElementConstructor<any>> | ReactElement<any, string | JSX ...

Develop a pair of linked drop-down menus using JSON information

I am currently developing a feature that involves creating two dependent lists using values extracted from a JSON variable. The data is structured like this: { "Name1": [ { "Name1_Nickname": "ID1" } ], "Name2": ...

Troubleshooting: Why is my switch-case statement in TypeScript not functioning as expected

Here is a simple switch case scenario: let ca: string = "2"; switch (ca) { case "2": console.log("2"); case "1": console.log("1"); default: console.log("default"); } I am puzzled by the output of this code, which is as follows: 2 1 defa ...

"Unveiling the power of canvas layers in creating dynamic

Curious about the process of achieving this task. Draw, pause for 1 second. Draw frame 2, pause for 1 second. Draw frame 3, pause for 1 second. Clear animation canvas. I believe the solution involves working on an overlapping canvas to avoid being erased ...

The object prototype can only be an instance of an Object or null; any other value will

While attempting to create a codesandbox in order to replicate a bug, I encountered an additional issue. You can view my codesandbox here: https://codesandbox.io/s/vue-typescript-example-o7xsv The error message states: Object prototype may only be an ...

AngularJS directives and controller scope

I'm looking to create a custom directive in AngularJS that generates a div element as a title and a ul list below it. The title should be customizable through an attribute, while the list content is controlled by a designated controller. Check out t ...