router锚点和html锚点,hash模式下Vue-router页面返回锚点(scroll behavior)实现
在普通頁面中,點擊瀏覽器的返回按鈕,在返回到上一頁時會處在上次瀏覽的位置。單頁面應用中,由于始終是同一個頁面, 因此需要自行實現頁面返回時的錨點。Vue-router的Scroll Behavior可以用于解決這個問題,但是只能應用在HTML5 history模式。本文實現了在hash模式下的錨點跳轉。
錨點位置存儲
Vue-router要求在HTML5 history模式下,是為了使用pushState、replaceState API以及popstate事件:
1
2
3
4
5
6
7
8
9// Vue-router中的push方法
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(location, route => {
pushState(cleanPath(this.base + route.fullPath))// 存儲頁面錨點位置
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
}, onAbort)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// pushState方法
export function pushState (url?: string, replace?: boolean){
saveScrollPosition()
// try...catch the pushState call to get around Safari
// DOM Exception 18 where it limits to 100 pushState calls
const history = window.history
try {
if (replace) {
history.replaceState({ key: _key }, '', url)
} else {
_key = genKey()
history.pushState({ key: _key }, '', url)
}
} catch (e) {
window.location[replace ? 'replace' : 'assign'](url)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16export function setupScroll (){
window.addEventListener('popstate', e => {
saveScrollPosition();
if (e.state && e.state.key) {
setStateKey(e.state.key);
}
})
}
window.addEventListener('popstate', e => {
this.transitionTo(getLocation(this.base), route => {
if (expectScroll) {
handleScroll(router, route, this.current, true)
}
})
})
在hash模式下需要我們自己記錄錨點位置。可以維護一個與history相同的數組,每次頁面跳轉時在Vue-router提供的鉤子函數中遍歷數組,存儲錨點位置。
錨點滾動
Vue-router本身提供了scrollBehavior方法,用來進行錨點跳轉。但是該方法只能用在HTML5 history模式下。研究了一下其源碼:vue-router/src/util/scroll.js,發現也是使用window.scrollTo()來進行頁面的滾動。重要的是設置滾動的時機,應當在下一個頁面繪制完成后進行跳轉(wait until re-render finishes before scrolling)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// wait until re-render finishes before scrolling
router.app.$nextTick(() => {
let position = getScrollPosition();
const shouldScroll = behavior(to, from, isPop ? position : null);
if (!shouldScroll) {
return;
}
const isObject = typeof shouldScroll === 'object';
if (isObject && typeof shouldScroll.selector === 'string') {
const el = document.querySelector(shouldScroll.selector);
if (el) {
position = getElementPosition(el);
} else if (isValidPosition(shouldScroll)) {
position = normalizePosition(shouldScroll);
}
}
else if (isObject && isValidPosition(shouldScroll)) {
position = normalizePosition(shouldScroll);
}
if (position) {
window.scrollTo(position.x, position.y);
}
})
我們目前已經自行記錄了錨點,因此可以在router中模仿一個跳轉過程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16router.beforeEach((to, from, next) => {
next();
// 重要的是設置跳轉的時機。過早的話頁面還沒加載完成,高度不夠導致不能滾動。過晚的話會造成頁面的閃爍。
router.app.$nextTick(() => {
// 獲取history數組中的最后一個browserHistoryLast
if (to.fullPath === browserHistoryLast.path
&& browserHistoryLast.pos) {
document.body.scrollTop = browserHistoryLast.pos;
}
else {
document.body.scrollTop = 0;
}
});
});
在router的鉤子函數中,調用next之后,在$nextTick中進行頁面的滾動,即可達到和scrollBehavior相似的效果。
需要注意的是,各個頁面的數據應當存儲在vuex中,不能每次進入頁面都發送請求(即使不錨點也應當這么做)。否側因為返回時頁面還在請求數據,不能達到錨點的效果。
關于過場動畫
如果頁面跳轉有過場動畫存在,非常容易在錨點滾動時發生閃爍。嘗試了幾種方式,都沒能達到很好效果。
嘗試過的方法的思路在這里記錄一下,這些方法都會有很大的抖動閃爍,根本原因還是頁面跳轉的時機不對:
返回到有存儲pos的舊頁面時,在onTransitionAfterEnter中將頁面滾動到記錄的位置。打開新頁面時,在onTransitionBeforeStart中將頁面滾動設置為0,確保新頁面在頂部。
vue-router的過渡動畫使用的是absolute定位+transform。因此嘗試了給頁面設置top值來消除閃爍。在跳轉前給當前頁面設置與目標頁面滾動位置相同的top值,在滾動結束后由于不再是absolute定位,top值不再生效,沒有閃爍發生。在返回時,列表頁會首先絕對定位到首頁要滾動的位置(此時會有閃爍),之后直接跳轉到首頁。閃爍集中在返回過渡效果之前。
其他問題全局mixin中不能寫組件中的過渡鉤子,如beforeRouteEnter等,會報錯。
computed只有在vuex中的變量變化時,才會進行更新。import進來的值不行。
在onTransitionBeforeStart修改變量,不會使from頁面中的computed更新。如果想要在頁面跳轉時更新from頁面的computed,需要在router的鉤子函數中進行修改,在this.$nextTick中調用next()。
總結
以上是生活随笔為你收集整理的router锚点和html锚点,hash模式下Vue-router页面返回锚点(scroll behavior)实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 文件的输入和输出:流和缓冲区的概念和文件
- 下一篇: Oracle密码过期问题 ORA-280