-
-
Notifications
You must be signed in to change notification settings - Fork 5.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Need a way to set what scroll position is saved #1187
Comments
Hi Brad. just thinking out loud: I think this won't be so easy to do since those two things - the scrollposition the router saves right now, and the scrollpostion of an arbitrary element - have not much in common, technically. One works with the Also, this functionality is not necessarily tied to router components, but could be useful to all sorts of components, which makes me think that it would be better to do this in a generic plugin/component ... |
The |
Cool, thanks for your feedback. Regarding my example app structure, I'd like to keep the router's functionality exactly as is, i.e. have it call const router = new VueRouter({
mode: 'history',
routes,
saveScrollPosition() {
let el = document.querySelector('.page-content')
return { x: el.scrollLeft, y: el.scrollTop }
}
}) |
@LinusBorg see my issue's program #1249 , is that possible to realize? |
IMO, we should set a scrollEl property in main router config. Thus allowing us to use a specific scroll container for both scrollBehavior and scroll position. What's everyone's opinion on this? |
You don't need special functions for this, before/afterEach would be a much better place to put these kinds of things |
It would be nice if we could somehow define from which element we want to save the scroll position.
|
I think this would be a pretty good and obvious feature to add :D |
@blake-newman I disagree, I think that each router-view (component) should define it's In terms of encapsulation, each component could be from a different source, as an app could be composed of many separate components from different authors on the web. Each component know's which child element within needs to scroll. It would be inappropriate to let the app decide what element within all components should be scrollable (as you would have to go into each component and add the scroll class/id). If anything, vue should implement an html directive ie: This way it:
|
@yyx990803 Does vue-router save scroll position of the Also, it seems that vue-router should NOT implement this at all...scrolling a container that is not supported has unintended side-effects ex: safari scroll to top doesnt work, mobile pull to reload can get trapped, scrolling can get trapped by an outer element and require two taps to scroll, etc. |
Those are all unfortunate side-effects, but mobile browsers ignoring |
On mobile browsers, the browser url bar/back buttons (think mobile safari/chrome) are hidden according to scroll position on the window/body. If you use |
Any update on this? I want to catch if the savedPosition has been set, and if possible read out the x/y and overrule this. Thanks! |
Just as a reference, after some head scratching, here is a pure vue solution (no jquery needed) In the root component, give the scrolling element a
Hook in a afterEach route guard so the root element knows when navigation happens. In that route guard, capture the current scroll position of the referenced element and store it for the from route, then restore the old position of the to route. Also, provide a method for restoring a scroll position. (This is coffeescript, sorry. I'm confident you can deal with it)
Finally, in the child component, restore the scroll position if wanted:
|
Hello everyone! |
I save scroll states of a child component by saving $refs.scrollElement.scrollTop on beforeRouteLeave, and restore it at beforeRouteEnter. This requires the to be wrapped in (or to simply have someplace else to store the scroll data, like vuex), so that the scroll data is available for routeBeforeEnter. E.g: ` Bunch of content ` I'm fairly certain this method could be made into a plugin of some sort. I hope this helped anyone! |
A very simple way to do it in TypeScript:
|
contain this mixins in your router page. export default { when your page rendered, calling the function to set scroll position. |
This behavior would be incredibly useful to get scrolling working without requiring html history usage. And would render libraries such as https://github.com/jeneser/vue-scroll-behavior unnecessary (at least when using html history mode), since we could just use the vue-router behavior to do such. As a side note, it would be nice if the |
Extending @iBrazilian2 's solution, you can leverage the // router.js
const scrollableElementId = 'id-of-your-scrollable-element'; // change id
const scrollPositions = Object.create(null);
const router = new VueRouter({
mode: 'history',
routes,
scrollBehavior(to, from, savedPosition) {
const element = document.getElementById(scrollableElementId);
if (savedPosition && element !== null && to.name in scrollPositions) {
console.log(
'%c%s',
'color:hotpink;',
'scrollBehavior: navigating to history entry, scroll to saved position',
);
element.scrollTop = scrollPositions[to.name];
} else {
console.log('%c%s', 'color:hotpink;', 'navigating to new history entry, scroll to top');
element.scrollTop = 0;
}
},
});
router.beforeEach((to, from, next) => {
const element = document.getElementById(scrollableElementId);
if (element !== null) {
scrollPositions[from.name] = element.scrollTop;
}
next();
}); |
Here's my solution: <script>
const savedPosition = {
x: 0,
y: 0,
}
export default {
mounted () {
this.$refs.scrollable.scrollTo({
left: savedPosition.x,
top: savedPosition.y,
})
},
beforeDestroy () {
savedPosition.x = this.$refs.scrollable.scrollLeft
savedPosition.y = this.$refs.scrollable.scrollTop
},
}
</script> |
the original suggestion would be a good use case to add, am wondering if this is still being considered? |
In my case, Nothing worked for me `<transition @before-enter="scrollTop" mode="out-in" appear> methods: { |
For those who want the scrollBehavior to apply to another element instead of Object.defineProperty(window, 'pageXOffset', {
get() { return document.querySelector('main')?.scrollLeft ?? 0; },
});
Object.defineProperty(window, 'pageYOffset', {
get() { return document.querySelector('main')?.scrollTop ?? 0; },
});
Object.defineProperty(window, 'scrollTo', {
value: (option: { top: number, left: number }) => {
let els = document.querySelectorAll('main');
let el = els[els.length - 1];
el?.scrollTo(option.left, option.top);
},
}); In my case, my scroll container is the |
The realization I have made that if you use the container to show the data - the there is one page that shows the infinite list of items and is using
On the non-cached pages you simply use:
EDIT: nevermind here are 2 functions.
and
in script tag
in
put it at the end, just in case. Because order matters. If you have other edit 2: unfortunately it looks like |
Here is an alternative implementation (inspired by @unamix): const SCROLL_CONTAINER_ID = 'content';
const scrollPositions = {};
function scrollBehavior(to, from, savedPosition) {
if (to.fullPath === from.fullPath) {
return;
}
const isNavigationForward = (savedPosition === null);
const contentEl = document.getElementById(SCROLL_CONTAINER_ID) as HTMLElement;
console.assert(contentEl !== null, 'Scroll container not found');
if (isNavigationForward) {
scrollPositions[from.fullPath] = {
top: contentEl.scrollTop,
left: contentEl.scrollLeft
};
contentEl.scroll({
top: 0,
left: 0
});
} else {
const savedPosition = scrollPositions[to.fullPath];
if (savedPosition) {
contentEl.scroll(savedPosition);
}
delete scrollPositions[to.fullPath];
}
}; This implementation relies on a fact that |
Expanding on @unamix and @kryvonos-v, here's what works for me in Nuxt 3, including In the 1Store the scroll position before leaving the route. This can be done easily in a plugin: plugins/router.ts export default defineNuxtPlugin(() => {
const scrollElement = '#scroll-element'
const scrollPositions = useState('savedScrollPositions', (): Record<string, ScrollPosition> => ({}))
const router = useRouter()
router.beforeEach((_to, from, next) => {
const el = document.querySelector(scrollElement)
if (el) {
scrollPositions.value[from.fullPath] = {
top: el.scrollTop,
left: el.scrollLeft,
}
}
next()
})
}) 2Restoring the position when navigating forwards or backward via router options: app/router.options.ts import type { RouterOptions } from '@nuxt/schema'
export default <RouterOptions> {
scrollBehavior(to, _from, savedPosition) {
const scrollElement = document.querySelector('#scroll-element')
if (scrollElement) {
let scrollPosition: ScrollPosition
if (savedPosition) {
const savedScrollPositions = useState('savedScrollPositions', (): Record<string, ScrollPosition> => ({}))
scrollPosition = savedScrollPositions.value[to.fullPath]
}
if (!scrollPosition) {
scrollPosition = { top: 0, left: 0 }
}
scrollElement.scrollTop = scrollPosition!.top
}
},
} To keep dynamic lists alive, we also need to add app.vue <template>
<div>
<NuxtLayout>
<NuxtPage :keepalive="{}" />
</NuxtLayout>
</div>
</template> |
For Vue Router 4 (Vue 3), together with @posva we made a userland solution to support custom and multiple scrolling targets: https://github.com/antfu/vue-router-better-scroller Feel free to give a try and create issues in that repo if you have any feedback! |
This right here should be the official implementation, As a suggestion I would probably expose the types. Here is how I'm using it in my nuxt 3 project: // plugins/scrollBehavior.client.ts
import { createRouterScroller } from "vue-router-better-scroller";
interface SavedPosition {
top?: number;
left?: number;
behavior?: ScrollOptions["behavior"];
}
function scrollHandler({ savedPosition }: { savedPosition?: SavedPosition }): SavedPosition {
return savedPosition || { top: 0, left: 0, behavior: "smooth" };
}
export default defineNuxtPlugin(({ vueApp }) => {
vueApp.use(
createRouterScroller({
selectors: {
window: scrollHandler,
body: scrollHandler,
".scrollable": scrollHandler,
},
})
);
}); |
Similar to the way you can define a
scrollBehavior
function on a router instance, I need a way to define how thesavedPosition
value gets computed/populated. I need to override the default behavior which stores the window's scroll position, and instead store the scroll position of another element.My application uses a structure where the
document
/window
stays static, and content scrolls inside of a container. Example: http://codepen.io/brad426/pen/pezRgeThe text was updated successfully, but these errors were encountered: