Search code examples
vue.jsnuxt.jsvue-router

Scrolling a page in vue-router only after loading the components


According vue-router documentation (https://router.vuejs.org/guide/advanced/scroll-behavior.html#async-scrolling) you can do "async scrolling". I have a single page app where the height of the page varies page by page. When navigating back, I would like to scroll the page to the same position where the user clicked the link.

Now, immediately when navigating back the component for the page has not been loaded fully and the overall height of the page is not long enough. The scroll to savedPosition will only go to the bottom of the page before the component loads.

I can do this in nuxt.config.js

 router: {
    scrollBehavior (to, from, savedPosition) {
      if (savedPosition)
      {
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            resolve(savedPosition)
          }, 300)
        })
      }

That will wait 300ms, around the time the page height is resolved, and only then scroll down. But this is not optimal. What if the loading of the component takes longer?

How would you implement vue-router scrolling only after the component on the page has loaded?

Can I listen to events in the page in the vue-router scrollBehavior? Could I somehow wait for the Vue lifecycle hooks to trigger the scroll? Can I get a trigger about the page height changing?


Solution

  • With the ResizeObserver API you can add an event listener on element height changes:

    // create an Observer instance to listen to height changes on an object
    const resizeObserver = new ResizeObserver(entries => {
        // Check if the height is enough to scroll to that point
        if(entries[0].target.clientHeight >= savedPosition.top + screen.height) {
            // Then resolve to trigger the scroll and disconnect the observer
            resolve(savedPosition);
            resizeObserver.disconnect();
        }
    });
    // start observing a DOM node
    resizeObserver.observe(document.body);
    

    Here's a complete example:

    scrollBehavior(to, from, savedPosition) {
        return new Promise((resolve, reject) => {
            if(savedPosition) {
                // create an Observer instance
                const resizeObserver = new ResizeObserver(entries => {
                    if(entries[0].target.clientHeight >= savedPosition.top + screen.height) {
                        resolve(savedPosition);
                        resizeObserver.disconnect();
                    }
                });
                
                // start observing a DOM node
                resizeObserver.observe(document.body);
            } else {
                resolve({ top: 0 });
            }
        });
    }