Skip to content
This repository was archived by the owner on Mar 7, 2025. It is now read-only.

Commit 2286ecd

Browse files
krmax44egoist
authored andcommitted
fix: Scroll to anchor not working #213 (#214)
closes #213 * fix: #213 * add scroll on initial load * fix: initial page load scroll on Chromium * remove css.escape * remove unnecessary code * avoid selector escaping issues
1 parent 1561c61 commit 2286ecd

File tree

6 files changed

+157
-82
lines changed

6 files changed

+157
-82
lines changed

packages/saber/vue-renderer/app/create-app.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import ClientOnly from './components/ClientOnly'
1010
import extendBrowserApi from '#cache/extend-browser-api'
1111
import injectConfig from './helpers/inject-config'
1212
import setTransition from './helpers/set-transition'
13+
import scrollHandler from './helpers/scroll-handler'
1314

1415
Vue.config.productionTip = false
1516

@@ -74,6 +75,13 @@ export default context => {
7475
transition: null
7576
}
7677
},
78+
mounted() {
79+
scrollHandler(
80+
this.$router,
81+
this.$router.currentRoute,
82+
this.$router.currentRoute
83+
)
84+
},
7785
render(h) {
7886
const transition = Object.assign({}, this.transition)
7987
const listeners = {}
@@ -89,7 +97,9 @@ export default context => {
8997
})
9098
const beforeEnter = listeners['before-enter']
9199
listeners['before-enter'] = el => {
92-
this.$emit('trigger-scroll')
100+
this.$nextTick(() => {
101+
this.$emit('trigger-scroll')
102+
})
93103
beforeEnter && beforeEnter(el)
94104
}
95105
const children = [
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
modified after Vue Router's scroll util
3+
https://github.com/vuejs/vue-router/blob/3c7d8ab20f9c716652e92065767a5a44ffb21c13/src/util/scroll.js
4+
*/
5+
6+
/**
7+
*
8+
* @param {object} router Vue Router instance
9+
* @param {object} to Destination route
10+
* @param {object} from Starting route
11+
*/
12+
export default function(router, to, from) {
13+
if (!router.app) {
14+
return
15+
}
16+
17+
const behavior = router.options.scrollBehavior
18+
if (!behavior) {
19+
return
20+
}
21+
22+
// wait until re-render finishes before scrolling
23+
router.app.$nextTick(() => {
24+
const shouldScroll = behavior.call(router, to, from, null)
25+
26+
if (!shouldScroll) {
27+
return
28+
}
29+
30+
if (typeof shouldScroll.then === 'function') {
31+
shouldScroll
32+
.then(shouldScroll => {
33+
scrollToPosition(shouldScroll)
34+
})
35+
.catch(err => {
36+
if (process.env.NODE_ENV !== 'production') {
37+
console.error(err)
38+
}
39+
})
40+
} else {
41+
scrollToPosition(shouldScroll)
42+
}
43+
})
44+
}
45+
46+
function getElementPosition(el, offset) {
47+
const docEl = document.documentElement
48+
const docRect = docEl.getBoundingClientRect()
49+
const elRect = el.getBoundingClientRect()
50+
return {
51+
x: elRect.left - docRect.left - offset.x,
52+
y: elRect.top - docRect.top - offset.y
53+
}
54+
}
55+
56+
function isValidPosition(obj) {
57+
return isNumber(obj.x) || isNumber(obj.y)
58+
}
59+
60+
function normalizePosition(obj) {
61+
return {
62+
x: isNumber(obj.x) ? obj.x : window.pageXOffset,
63+
y: isNumber(obj.y) ? obj.y : window.pageYOffset
64+
}
65+
}
66+
67+
function normalizeOffset(obj) {
68+
return {
69+
x: isNumber(obj.x) ? obj.x : 0,
70+
y: isNumber(obj.y) ? obj.y : 0
71+
}
72+
}
73+
74+
function isNumber(v) {
75+
return typeof v === 'number'
76+
}
77+
78+
function scrollToPosition(shouldScroll) {
79+
let position
80+
const isObject = typeof shouldScroll === 'object'
81+
if (isObject && typeof shouldScroll.selector === 'string') {
82+
const el = document.getElementById(shouldScroll.selector.substr(1))
83+
if (el) {
84+
let offset =
85+
shouldScroll.offset && typeof shouldScroll.offset === 'object'
86+
? shouldScroll.offset
87+
: {}
88+
offset = normalizeOffset(offset)
89+
position = getElementPosition(el, offset)
90+
} else if (isValidPosition(shouldScroll)) {
91+
position = normalizePosition(shouldScroll)
92+
}
93+
} else if (isObject && isValidPosition(shouldScroll)) {
94+
position = normalizePosition(shouldScroll)
95+
}
96+
97+
if (position) {
98+
window.scrollTo(position.x, position.y)
99+
}
100+
}

packages/saber/vue-renderer/app/router.js

Lines changed: 46 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -31,65 +31,57 @@ if (process.client) {
3131
}
3232

3333
export default () => {
34-
const createRouter = routes => new Router({
35-
mode: 'history',
36-
routes,
37-
scrollBehavior(to, from, savedPosition) {
38-
// if the returned position is falsy or an empty object,
39-
// will retain current scroll position.
40-
let position = false
41-
42-
// if no children detected and scrollToTop is not explicitly disabled
43-
if (
44-
to.matched.length < 2 &&
45-
to.matched.every(
46-
r => r.components.default.scrollToTop !== false
47-
)
48-
) {
49-
// scroll to the top of the page
50-
position = { x: 0, y: 0 }
51-
} else if (
52-
to.matched.some(r => r.components.default.scrollToTop)
53-
) {
54-
// if one of the children has scrollToTop option set to true
55-
position = { x: 0, y: 0 }
56-
}
57-
58-
// savedPosition is only available for popstate navigations (back button)
59-
if (savedPosition) {
60-
position = savedPosition
61-
}
62-
63-
return new Promise(resolve => {
64-
// wait for the out transition to complete (if necessary)
65-
router.app.$once('trigger-scroll', () => {
66-
// coords will be used if no selector is provided,
67-
// or if the selector didn't match any element.
68-
if (to.hash) {
69-
let hash = to.hash
70-
// CSS.escape() is not supported with IE and Edge.
71-
if (
72-
typeof window.CSS !== 'undefined' &&
73-
typeof window.CSS.escape !== 'undefined'
74-
) {
75-
hash = '#' + window.CSS.escape(hash.substr(1))
76-
}
77-
try {
78-
if (document.querySelector(hash)) {
34+
const createRouter = routes =>
35+
new Router({
36+
mode: 'history',
37+
routes,
38+
scrollBehavior(to, from, savedPosition) {
39+
// if the returned position is falsy or an empty object,
40+
// will retain current scroll position.
41+
let position = false
42+
43+
// if no children detected and scrollToTop is not explicitly disabled
44+
if (
45+
to.matched.length < 2 &&
46+
to.matched.every(r => r.components.default.scrollToTop !== false)
47+
) {
48+
// scroll to the top of the page
49+
position = { x: 0, y: 0 }
50+
} else if (to.matched.some(r => r.components.default.scrollToTop)) {
51+
// if one of the children has scrollToTop option set to true
52+
position = { x: 0, y: 0 }
53+
}
54+
55+
// savedPosition is only available for popstate navigations (back button)
56+
if (savedPosition) {
57+
position = savedPosition
58+
}
59+
60+
return new Promise(resolve => {
61+
const fulfill = () => {
62+
// coords will be used if no selector is provided,
63+
// or if the selector didn't match any element.
64+
if (to.hash) {
65+
if (document.getElementById(to.hash.substr(1))) {
7966
// scroll to anchor by returning the selector
80-
position = { selector: hash }
67+
position = { selector: to.hash }
68+
} else {
69+
// scroll to top if anchor does not exist and position is not already set
70+
position = position || { x: 0, y: 0 }
8171
}
82-
} catch (e) {
83-
console.warn(
84-
'Failed to save scroll position. Please add CSS.escape() polyfill (https://github.com/mathiasbynens/CSS.escape).'
85-
)
8672
}
73+
resolve(position)
74+
}
75+
76+
// wait for the out transition to complete (if necessary)
77+
if (to.path === from.path) {
78+
fulfill()
79+
} else {
80+
router.app.$once('trigger-scroll', fulfill)
8781
}
88-
resolve(position)
8982
})
90-
})
91-
}
92-
})
83+
}
84+
})
9385

9486
const router = createRouter(routes)
9587

website/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
},
88
"dependencies": {
99
"date-fns": "1.30.1",
10-
"jump.js": "1.0.2",
1110
"markdown-it-footnote": "3.0.1",
1211
"nprogress": "0.2.0",
1312
"prismjs": "1.16.0",

website/src/mixins/doc.js

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,4 @@
1-
import jump from 'jump.js'
2-
31
export default {
4-
watch: {
5-
'$route.hash': {
6-
handler(hash) {
7-
this.$nextTick(() => {
8-
if (hash) {
9-
const el = document.getElementById(hash.slice(1))
10-
if (el) {
11-
jump(el, {
12-
duration: 0,
13-
offset: -(document.querySelector('.header').clientHeight + 20)
14-
})
15-
}
16-
}
17-
})
18-
},
19-
immediate: true
20-
}
21-
},
22-
232
mounted() {
243
// Make footnotes focusable
254
const items = this.$el.querySelectorAll('.footnote-item,.footnote-ref a')

website/yarn.lock

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,6 @@ indexes-of@^1.0.1:
149149
resolved "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
150150
integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc=
151151

152-
jump.js@1.0.2:
153-
version "1.0.2"
154-
resolved "https://registry.npmjs.org/jump.js/-/jump.js-1.0.2.tgz#e0641b47f40a38f2139c25fda0500bf28e43015a"
155-
integrity sha1-4GQbR/QKOPITnCX9oFAL8o5DAVo=
156-
157152
lodash._reinterpolate@~3.0.0:
158153
version "3.0.0"
159154
resolved "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"

0 commit comments

Comments
 (0)