Skip to content
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

Make scrollBehavior work with transitions by handling a promise (fixes #1263) #1758

Merged
merged 6 commits into from
Oct 11, 2017
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 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,12 +20,13 @@ 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 = async function (to, from, savedPosition) {
if (savedPosition) {
// savedPosition is only available for popstate navigations.
return savedPosition
} else {
const position = {}
let delay = 500
// new navigation.
// scroll to anchor by returning the selector
if (to.hash) {
Expand All @@ -35,14 +36,20 @@ const scrollBehavior = (to, from, savedPosition) => {
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)) {
// cords will be used if no selector is provided,
// coords will be used if no selector is provided,
// or if the selector didn't match any element.
position.x = 0
position.y = 0
}
// wait for the out transition to complete (if necessary)
await (new Promise(resolve => setTimeout(resolve, delay)))
// if the returned position is falsy or an empty object,
// will retain current scroll position.
return position
Expand Down Expand Up @@ -72,7 +79,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
42 changes: 25 additions & 17 deletions src/util/scroll.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,28 +36,16 @@ 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)
}
Promise.resolve(shouldScroll).then((shouldScroll) => {
scrollToPosition(shouldScroll, position)
})
})
}

Expand Down Expand Up @@ -109,3 +97,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)
}
}
12 changes: 11 additions & 1 deletion test/e2e/specs/scroll-behavior.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
module.exports = {
'scroll behavior': function (browser) {
const TIMEOUT = 2000

browser
.resizeWindow(1280, 800)
.url('http://localhost:8080/scroll-behavior/')
.waitForElementVisible('#app', 1000)
.assert.count('li a', 5)
Expand All @@ -10,11 +13,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 All @@ -25,6 +30,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 @@ -33,12 +39,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 @@ -48,7 +56,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