Skip to content

Commit

Permalink
feat: scrollBehavior accept returning a promise (#1758)
Browse files Browse the repository at this point in the history
  • Loading branch information
homerjam authored and yyx990803 committed Oct 11, 2017
1 parent a722b6a commit ce13b55
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 42 deletions.
60 changes: 36 additions & 24 deletions examples/scroll-behavior/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import VueRouter from 'vue-router'

Vue.use(VueRouter)

const Home = { template: '<div>home</div>' }
const Foo = { template: '<div>foo</div>' }
const Home = { template: '<div class="home">home</div>' }
const Foo = { template: '<div class="foo">foo</div>' }
const Bar = {
template: `
<div>
<div class="bar">
bar
<div style="height:500px"></div>
<p id="anchor" style="height:500px">Anchor</p>
Expand All @@ -20,32 +20,42 @@ const Bar = {
// - only available in html5 history mode
// - defaults to no scroll behavior
// - return false to prevent scroll
const scrollBehavior = (to, from, savedPosition) => {
const scrollBehavior = function (to, from, savedPosition) {
if (savedPosition) {
// savedPosition is only available for popstate navigations.
return savedPosition
} else {
const position = {}
// new navigation.
// scroll to anchor by returning the selector
if (to.hash) {
position.selector = to.hash
return new Promise(resolve => {
const position = {}
let delay = 500
// new navigation.
// scroll to anchor by returning the selector
if (to.hash) {
position.selector = to.hash

// specify offset of the element
if (to.hash === '#anchor2') {
position.offset = { y: 100 }
// specify offset of the element
if (to.hash === '#anchor2') {
position.offset = { y: 100 }
}

if (document.querySelector(to.hash)) {
delay = 0
}
}
// check if any matched route config has meta that requires scrolling to top
if (to.matched.some(m => m.meta.scrollToTop)) {
// coords will be used if no selector is provided,
// or if the selector didn't match any element.
position.x = 0
position.y = 0
}
}
// check if any matched route config has meta that requires scrolling to top
if (to.matched.some(m => m.meta.scrollToTop)) {
// cords will be used if no selector is provided,
// or if the selector didn't match any element.
position.x = 0
position.y = 0
}
// if the returned position is falsy or an empty object,
// will retain current scroll position.
return position
// wait for the out transition to complete (if necessary)
setTimeout(() => {
// if the returned position is falsy or an empty object,
// will retain current scroll position.
resolve(position)
}, delay)
})
}
}

Expand All @@ -72,7 +82,9 @@ new Vue({
<li><router-link to="/bar#anchor">/bar#anchor</router-link></li>
<li><router-link to="/bar#anchor2">/bar#anchor2</router-link></li>
</ul>
<router-view class="view"></router-view>
<transition name="fade" mode="out-in">
<router-view class="view"></router-view>
</transition>
</div>
`
}).$mount('#app')
6 changes: 6 additions & 0 deletions examples/scroll-behavior/index.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
<!DOCTYPE html>
<link rel="stylesheet" href="/global.css">
<style>
.fade-enter-active, .fade-leave-active {
transition: opacity .5s ease;
}
.fade-enter, .fade-leave-active {
opacity: 0
}
.view {
border: 1px solid red;
height: 2000px;
Expand Down
44 changes: 28 additions & 16 deletions src/util/scroll.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,27 +38,19 @@ export function handleScroll (

// wait until re-render finishes before scrolling
router.app.$nextTick(() => {
let position = getScrollPosition()
const 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) {
let offset = shouldScroll.offset && typeof shouldScroll.offset === 'object' ? shouldScroll.offset : {}
offset = normalizeOffset(offset)
position = getElementPosition(el, offset)
} else if (isValidPosition(shouldScroll)) {
position = normalizePosition(shouldScroll)
}
} else if (isObject && isValidPosition(shouldScroll)) {
position = normalizePosition(shouldScroll)
}

if (position) {
window.scrollTo(position.x, position.y)
if (typeof shouldScroll.then === 'function') {
shouldScroll.then(shouldScroll => {
scrollToPosition(shouldScroll, position)
})
} else {
scrollToPosition(shouldScroll, position)
}
})
}
Expand Down Expand Up @@ -111,3 +103,23 @@ function normalizeOffset (obj: Object): Object {
function isNumber (v: any): boolean {
return typeof v === 'number'
}

function scrollToPosition (shouldScroll, position) {
const isObject = typeof shouldScroll === 'object'
if (isObject && typeof shouldScroll.selector === 'string') {
const el = document.querySelector(shouldScroll.selector)
if (el) {
let offset = shouldScroll.offset && typeof shouldScroll.offset === 'object' ? shouldScroll.offset : {}
offset = normalizeOffset(offset)
position = getElementPosition(el, offset)
} else if (isValidPosition(shouldScroll)) {
position = normalizePosition(shouldScroll)
}
} else if (isObject && isValidPosition(shouldScroll)) {
position = normalizePosition(shouldScroll)
}

if (position) {
window.scrollTo(position.x, position.y)
}
}
7 changes: 6 additions & 1 deletion test/e2e/nightwatch.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ module.exports = {
'desiredCapabilities': {
'browserName': 'chrome',
'javascriptEnabled': true,
'acceptSslCerts': true
'acceptSslCerts': true,
'chromeOptions': {
'args': [
'window-size=1280,800'
]
}
}
},

Expand Down
11 changes: 10 additions & 1 deletion test/e2e/specs/scroll-behavior.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
module.exports = {
'scroll behavior': function (browser) {
const TIMEOUT = 2000

browser
.url('http://localhost:8080/scroll-behavior/')
.waitForElementVisible('#app', 1000)
Expand All @@ -10,11 +12,13 @@ module.exports = {
window.scrollTo(0, 100)
})
.click('li:nth-child(2) a')
.waitForElementPresent('.view.foo', TIMEOUT)
.assert.containsText('.view', 'foo')
.execute(function () {
window.scrollTo(0, 200)
window.history.back()
})
.waitForElementPresent('.view.home', TIMEOUT)
.assert.containsText('.view', 'home')
.assert.evaluate(function () {
return window.pageYOffset === 100
Expand Down Expand Up @@ -45,6 +49,7 @@ module.exports = {
window.scrollTo(0, 50)
window.history.forward()
})
.waitForElementPresent('.view.foo', TIMEOUT)
.assert.containsText('.view', 'foo')
.assert.evaluate(function () {
return window.pageYOffset === 200
Expand All @@ -53,12 +58,14 @@ module.exports = {
.execute(function () {
window.history.back()
})
.waitForElementPresent('.view.home', TIMEOUT)
.assert.containsText('.view', 'home')
.assert.evaluate(function () {
return window.pageYOffset === 50
}, null, 'restore scroll position on back again')

.click('li:nth-child(3) a')
.waitForElementPresent('.view.bar', TIMEOUT)
.assert.evaluate(function () {
return window.pageYOffset === 0
}, null, 'scroll to top on new entry')
Expand All @@ -68,7 +75,9 @@ module.exports = {
return document.getElementById('anchor').getBoundingClientRect().top < 1
}, null, 'scroll to anchor')

.click('li:nth-child(5) a')
.execute(function () {
document.querySelector('li:nth-child(5) a').click()
})
.assert.evaluate(function () {
return document.getElementById('anchor2').getBoundingClientRect().top < 101
}, null, 'scroll to anchor with offset')
Expand Down

0 comments on commit ce13b55

Please sign in to comment.