vuejs / router

🚦 The official router for Vue.js
https://router.vuejs.org/
MIT License
3.91k stars 1.19k forks source link

Add support to avoid scroll position calculation on router push #2393

Open index23 opened 4 days ago

index23 commented 4 days ago

What problem is this solving

Description

Vue Router currently calculates the scroll position on every router.push navigation by invoking the computeScrollPosition function, which accesses window.pageXOffset and window.pageYOffset. While this behavior is useful for maintaining scroll position, it triggers layout recalculations that can negatively impact performance, especially on low-powered devices.

Given that JavaScript applications built with Vue.js can run across a variety of platforms — from mobile phones to high-performance computers, embedded devices, and Smart TVs — it's essential to account for environments where layout-intensive operations can hinder the user experience.

Motivation

The layout calculation triggered by accessing scroll-related properties is particularly expensive on embedded devices and Smart TVs, where hardware constraints may limit performance. Reducing unnecessary layout operations is critical for ensuring smooth navigation and an optimal user experience on these platforms.

Applications on such devices couldn't use scroll behaviour at all and such feature is redudant for these platforms.

Currently, Vue Router does not offer a way to skip or control these scroll position calculations, resulting in performance overhead even when maintaining scroll position is unnecessary.

Proposed solution

We propose extending the Vue Router API with an option to disable scroll position calculations on navigation. This could be configured either globally or on a per-route basis, providing developers with more control over performance-critical scenarios.

const router = createRouter({
  history: createWebHistory(),
  routes,
  scrollPositioning: {
    calculateScrollPosition: false, // Disable scroll position calculation
  },
});

This feature would ensure that developers working on performance-sensitive applications — especially for embedded systems or Smart TVs — can reduce the performance impact associated with layout recalculations.

Another alternative solution could be to add third parameter to push function to avoid scroll position computation, like it has the buildState function.

function push(to, data, calculateScrollPosition) {
    // Add to current entry the information of where we are going
    // as well as saving the current position
    const currentState = assign({},
    // use current history state to gracefully handle a wrong call to
    // history.replaceState
    // https://github.com/vuejs/vue-router-next/issues/366
    historyState.value, history.state, {
        forward: to,
        scroll: calculateScrollPosition ? computeScrollPosition() : {
           left:0,
           top: 0
        },
    });
    if ((process.env.NODE_ENV !== 'production') && !history.state) {
        warn(`history.state seems to have been manually replaced without preserving the necessary values. Make sure to preserve existing history state if you are manually calling history.replaceState:\n\n` +
            `history.replaceState(history.state, '', url)\n\n` +
            `You can find more information at https://next.router.vuejs.org/guide/migration/#usage-of-history-state.`);
    }
    changeLocation(currentState.current, currentState, true);
    const state = assign({}, buildState(currentLocation.value, to, null), { position: currentState.position + 1 }, data);
    changeLocation(to, state, false);
    currentLocation.value = to;
}

Describe alternatives you've considered

A workaround could be manually overriding the window.pageXOffset and window.pageYOffset properties to skip layout calculations. However, this approach, modifying window object, could be error prone in the context of entire applications.

posva commented 4 days ago

What about not saving the position if no scrollBehavior is provided?

index23 commented 4 days ago

I am not sure if I understand how not to save the position, but I will try to answer. I've configured router with scrollBehaviour returning false, but same behaviour happens triggering computeScrollPosition execution.

createRouter({
  history: createWebHashHistory(),
  base: __dirname,
  scrollBehavior () {
    return false
  },
  routes: [
    {},
    // ...
  ]
})

@posva Did you mean this way?

posva commented 3 days ago

No, I mean that if no scrollBehavior option is passed, the router could just not compute the scroll

index23 commented 3 days ago

Ok, understand. This was my initial setup, without scrollBehaviour option, and in that case I've noticed computeScrollPosition execution. So this doesn't help.

ferferga commented 2 days ago

@index23 I think what @posva means is that, instead of adding a new scrollPositioning option, no computeScrollPosition is triggered if scrollBehaviour is undefined.

index23 commented 1 day ago

@ferferga Thanks for clarification. Unfortunately same behaviour happens, computeScrollPosition triggers, even if scrollBehaviour is undefined.

posva commented 1 day ago

it won't work until it's implemented, that's what I mean by "if no scrollBehavior option is passed, the router could ...". But it's okay, I was planning on implementing it this way already 😄

index23 commented 1 day ago

After one more iteration it is absolutely clear. 😂 That's great, there is no need for API changes.