Tips for preserving scroll position within a division following the redisplay of a division in Vue.js

Within my Laravel and Vue project, I have set up a feature to display posts in a scrollable area. This area is only visible when the 'showFeed' variable is true.

<div v-show="showFeed" class="scroll-container" v-scroll="handleDebouncedScroll">
    <div class="card mb-4" v-cloak v-for="(item,index) in posts" :key="item.id">
        <div v-bind:id=index class="border" >
            <user-feed-post
                ....
                :item="item">
            </user-feed-post>
        </div>
    </div>                
</div>

When a post is clicked, the details are displayed on a separate section of the page using a component. To achieve this, I simply set showFeed to false and showPostDetails to true.

<div v-if="showPostDetails" >
    <post-details
        ....
     >
          </post-details>    
 </div>

Upon closing the post-details component, I revert showPostDetails back to false and set showFeed to true again. The functionality works as intended, however, there is an issue where the posts are reloaded from the beginning position. Essentially, the initial post is displayed at the top, even if the user had scrolled down to view other posts before clicking on a post for more details. I have implemented a temporary workaround to return the scroll position back to where it was (e.g. tenth post), but I am looking for a better solution.

Does anyone have any suggestions on how to maintain the scroll position of the posts section when it is redisplayed?

Update: I came across information suggesting that using keep-alive (along with some code) might help achieve the desired outcome. Has anyone had experience working with keep-alive in a similar scenario?

Answer №1

For those utilizing the Vue Router, there is a way to create a composable that can be connected to your element in order to maintain the scrollbar position. In this demonstration, a composable is used in a base wrapper component and in a more intricate scenario with multiple scrollbars that need to be persisted.

scrollPosition.ts

import { onActivated, ref, Ref, unref } from "vue";
import { onBeforeRouteLeave } from "vue-router";

export function useSaveScrollPosition(el: Ref<HTMLElement>) {
  const scrollTop = ref(0);
  const scrollLeft = ref(0);

  onActivated(() => {
    const $el = unref(el);
    if ($el) {
      $el.scrollTop = scrollTop.value;
      $el.scrollLeft = scrollLeft.value;
    }
  });

  onBeforeRouteLeave(() => {
    const $el = unref(el);
    if ($el) {
      scrollTop.value = $el.scrollTop;
      scrollLeft.value = $el.scrollLeft;
    }
  });
}

RouteWrapper.vue

<template>
  <article ref="componentEl">
    <slot />
  </article>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { useSaveScrollPosition } from "../scrollPosition";

const componentEl = ref<HTMLElement>();
useSaveScrollPosition(componentEl);
</script>

ViewHome.vue

<template>
  <RouteWrapper style="height: 100px; overflow: auto">
    <LongContent :total-lines="50" />
  </RouteWrapper>
</template>

<script setup lang="ts">
import RouteWrapper from "./RouteWrapper.vue";
import LongContent from "./LongContent.vue";
</script>

ViewThing.vue

<template>
  <article style="height: 200px; display: flex">
    <button>A button</button>
    <section ref="elementOne" style="overflow: auto; flex: 1 1 0%">
      <LongContent :total-lines="200" />
    </section>
    <section ref="elementTwo" style="overflow: auto; width: 200px">
      <LongContent :total-lines="100" content-style="width: 400px" />
    </section>
  </article>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { useSaveScrollPosition } from "../scrollPosition";
import LongContent from "./LongContent.vue";

const elementOne = ref();
const elementTwo = ref();
useSaveScrollPosition(elementOne);
useSaveScrollPosition(elementTwo);
</script>

router.ts

import { createRouter, createWebHashHistory } from 'vue-router';
import ViewHome from '@/components/ViewHome.vue';
import ViewThing from '@/components/ViewThing.vue';

export const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    {
      path: '/home',
      name: 'home',
      component: ViewHome,
    },
    {
      path: '/thing',
      name: 'thing',
      component: ViewThing,
    },
  ],
});

App.vue

<template>
  <main style="display: flex; flex-direction: column">
    <nav style="margin-bottom: 1rem">
      <a href="#/home" style="margin-right: 1rem">Home</a>
      <a href="#/thing">Thing</a>
    </nav>
    <RouterView v-slot="{ Component }">
      <KeepAlive>
        <Component :is="Component" />
      </KeepAlive>
    </RouterView>
  </main>
</template>

<script setup lang="ts">
import { RouterView } from 'vue-router';
</script>

This approach allows for versatility in managing main components as well as individual elements, though it is specifically designed for use with the router due to the necessity of the onBeforeRouteLeave hook (credits). Implementing onDeactivated posed challenges as the element was inaccessible and scrollTop defaulted to 0.

While the desired directive functionality was considered, current limitations prevent access to activated/deactivated hooks (source). Additionally, it's important to note that the Vue Router's scrollBehavior hook primarily impacts navigation through browser back/forward actions, which may not align with all application scenarios.

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

"Troubleshooting ng-submit in AngularJS: How to Fix a Function That

I've encountered an issue with my angular-promise where a http method is not being called when a form is submitted using ng-submit. There are no errors, but it seems like the function is never executed. Here's the JavaScript code snippet: myapp. ...

ReactJS Scripts are throwing an error indicating that the function require is not defined

Just starting out with React and I discovered that we can integrate React by adding the necessary scripts to our main index.html file. I followed all the steps to include the script and even made sure to add Babel since I'm writing my main App in JSX. ...

Converting PHP arrays into JavaScript arrays with the help of json_encode()

Is there a way to pass an array from PHP to the JavaScript function console.log()? I'm currently simulating a database and need help with this specific task. Despite not having an array declared in my code, I attempted to use the .getJSON() function w ...

Navigate through a filtered list of parent items with the use of cursor arrows in AngularJS

My form contains an input with a dropdown, where I display JSON data like this: { "title": "Parent #1", "child" : [ {"title": "Child ##1"}, {"title": "Child ##2"}, {"title": "Child ##3"}, {"title": "Child ##4"} ...

Having trouble with installing Recharts through npm

When I try to install recharts using npm, I encounter the following error message in my console: npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to resolve dependency tree npm ERR! ...

What is the best way to pass a parameter with a slash in a GET request using jQuery's .ajax() function?

I'm currently faced with the task of generating a specific URL to make an API call using jQuery's .ajax() function: https://www.airnowapi.org/aq/forecast/zipCode/?format=application/json&zipCode=02144&date=2016-11-26&distance=25& ...

Retrieve the computed value of a cell in an Excel spreadsheet using Node.js

Utilizing node.js in tandem with the exceljs module to process my Excel sheets. Writing values into specific cells while others already contain formulas. Seeking a method to trigger those formulas and programmatically store the resultant values in the she ...

Attempting to reach <p> from numerous web pages

Currently, I am working on a project where I need to extract text data from various websites. I have successfully managed to retrieve the text data using <p> tags. I would really appreciate any assistance with this task. Thank you! ...

Removing a specific row in a database table and sending a boolean indicator to an API, all while keeping the original object intact

I'm currently working on a spa project using AngularJS and integrating an api with mvvm in C#. One issue I am facing is that when I click the delete button, it deletes the line but I only want to change a boolean flag to true on the server side while ...

What is the best way to trigger an API call every 10 seconds in Angular 11 based on the status response?

I am in need of a solution to continuously call the API every 10 seconds until a success status is achieved. Once the success status is reached, the API calls should pause for 10 seconds before resuming. Below is the code I am currently using to make the A ...

Fastify endpoint failing to respond to designated URL

Within my code, there is a router setup: fastify.get('/:link', (req, reply) => { req.params.url = req.host+req.url; reply.view("template.ejs",req.params); }); I am trying to capture URLs and process them in the template. All URLs are ...

CanvasJS showcasing a variety of pie charts

I need to generate multiple pie charts for a website, but I'm struggling with the idea of dynamically rendering them based on the required quantity. I know that I will have to create a maximum of 28 pie charts at once. My initial approach involves man ...

Ways to usually connect forms in angular

I created a template form based on various guides, but they are not working as expected. I have defined two models: export class User { constructor(public userId?: UserId, public firstName?: String, public lastName?: String, ...

How can we determine the nL and nH values using an ESC/POS command?

Looking to adjust the printer's head position using ESC / pos commands: ESC $ command sets the absolute horizontal position ESC $ nL nH Trying to figure out how to calculate the values for nL and nH? ...

Processing one file to submit two forms to the database in Express

I am facing an issue with two forms on one form.hbs page using the same process.js file. Each form is processed by a different submit button for separate occasions. The first submit button works correctly, processing the data in the process.js file and se ...

Is it possible to export files from Flash to CreateJS via the command line?

Is there a method to automatically run the createjs toolkit for Flash tool from the command line? I have multiple components that I need to export in bulk. Is it possible to do this in a batch process? ...

At times, the map may only be visible in the top left corner of its designated container

Currently, I am integrating the Google Maps API v3 for JavaScript into my project. However, I am facing an issue where the map only appears in the upper left corner under certain circumstances. To visualize this problem, you can visit this link and click ...

How can I close the menu when a menu item is clicked in a React.js application?

I am facing an issue where I want to close the menu when clicking on any menu item in React JS. The functionality works fine with a button icon but not with individual menu items. How can I resolve this problem? I have been trying to implement it using CSS ...

Tips on implementing JSON data into select2 plugin

I have been trying to integrate the select2 plugin into my project. I followed a tutorial from this link, but unfortunately, it's not functioning properly for me. Here is the JSON output: [ {"ime":"BioPlex TM"}, {"ime":"Aegis sym agrilla"}, ...

Using whitespace to format a document.write in JavaScript

I'm in the process of creating a dynamic table using JavaScript and a set of objects. I've managed to structure it, but now I require some extra white space between them, almost like tabbing them out. How can I achieve this efficiently with my cu ...