咱们在实际开发中会遇到一个问题:
我们在一个很长的列表页往下拉,然后点击列表中的某一个数据进入到详情页查看。此时我们决定返回列表也继续查看列表。
很多情况下,由于列表页的组件已经被销毁,所以我们返回到列表页后页面会置顶,不得不又重新下拉查看列表,这样就做了很多没有必要的操作,也是不符合用户的预期。
用户希望当我查看玩详情页以后返回,返回列表页的位置是刚刚浏览的位置
这种情况有什么好的解决办法呢?
这里带来了3种解决方案:
1.使用<keep-alive> 缓存,即不销毁列表页
APP.js中
<template>
<div id="app">
<!-- <router-view/> -->
<keep-alive>
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive" />
</div>
</template>
router.js中
routes: [
{
path: '/',
name: 'List',
//component: List
component: () => import('./views/index/list.vue'),
meta: {
keepAlive: true // 需要缓存
}
},
{
path: '/content/:contentId',
name: 'content',
component: () => import('./views/index/content.vue'),
meta: {
keepAlive: false // 不需要缓存
}
},
]
详情页面不需要缓存,列表页面需要缓存
2.使用路由守卫
原理就是在beforRouterLeave的路由钩子记录当前页面滚动位置
//在页面离开时记录滚动位置,这里的this.scrollTop可以保存在vuex的state或者浏览器本地
beforeRouteLeave (to, from, next) {
this.scrollTop = document.documentElement.scrollTop || document.body.scrollTop
next()
},
//进入该页面时,用之前保存的滚动位置赋值
beforeRouteEnter (to, from, next) {
next(vm => {
document.body.scrollTop = vm.scrollTop
})
},
这里的this.scrollTop可以保存在vuex的state或者浏览器本地
3.使用vue-router方法scrollBehavior(推荐)
router.js中
const scrollBehavior = function scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
// savedPosition 会在你使用浏览器前进或后退按钮时候生效
// savedPosition: 会记录滚动条的坐标,点击"后退/前进" 时的记录值(x:?,y:?)
return savedPosition;
}else {
return { x: 0, y: 0 }; // 期望滚动的位置
}
};
const router = new Router({
routes,
scrollBehavior,
});
该方案直接在路由进行处理,兼容每个页面并且页面加载完后并也不会产生1px的滚动位置。
scrollBehavior (to, from, savedPosition)
方法接收to
和from
路由对象。第三个参数savedPosition
当且仅当popstate
导航(通过浏览器的 前进/后退 按钮触发)时才可用。参数:
to:要进入的目标路由对象,到哪里去
from:离开的路由对象,从哪儿来
savedPosition: 会记录滚动条的坐标,点击"后退/前进" 时的记录值(x:?,y:?)
{ x: number, y: number }
{ selector: string, offset? : { x: number, y: number }}
(offset 只在 2.6.0+ 支持)
滚动行为
我们可以通过 vue-router
自定义路由切换时页面如何滚动。比如,当跳转到新路由时,页面滚动到某个位置;切换路由时页面回到之前的滚动位置。
当创建路由实例时,我们只需要提供一个 scrollBehavior
方法:
const router = createRouter({
history: createWebHashHistory(),
routes: [...],
scrollBehavior (to, from, savedPosition) {
// return 期望滚动到哪个的位置
}
})
scrollBehavior
函数接收 to
和 from
路由对象。第三个参数 savedPosition
,只有当这是一个 popstate
导航时才可用(点击浏览器的后退/前进按钮,或者调用 router.go()
方法)
滚动到固定距离
该函数可以返回一个 ScrollToOptions
位置对象:
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
// 始终滚动到顶部
return { top: 0 }
},
})
滚动到元素位置
可以通过 el
传递一个 CSS
选择器或一个 DOM
元素。在这种情况下,top
和 left
将被视为该元素的相对偏移量。
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
// 始终在元素 #main 上方滚动 10px
return {
// el: document.getElementById('main'),
el: '#main',
top: -10,
}
},
})
滚动到锚点位置
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
if (to.hash) {
return {
el: to.hash,
}
}
},
})
滚动到之前的位置
在按下浏览器 后退/前进
按钮,或者调用 router.go()
方法时,页面会回到之前的滚动位置:
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { top: 0, behavior: 'smooth' }
}
},
})
注意:如果返回一个 false
的值,或者是一个空对象
,则不会发生滚动。我们还可以在返回的对象中添加 behavior: 'smooth'
,让滚动更加丝滑。
scrollIntoView()
解决方案2:
如果你想要在特定的按钮点击事件中实现平滑滚动,你可以使用 scrollIntoView
方法。
scrollIntoView()
方法将调用它的元素滚动到浏览器窗口的可见区域顶部。
element.scrollIntoView(); // 等同于 element.scrollIntoView(true)
element.scrollIntoView(alignToTop); //布尔参数。接受布尔值主要还是为了兼容不支持平滑滚动(老版)的浏览器
element.scrollIntoView(scrollIntoViewOptions); //对象参数
参数:
alignToTop:
当传入参数true时,相当于{behavior: ‘auto’, block: ‘start’, inline: ‘nearest’}
当传入参数false时,相当于{behavior: ‘auto’, block: ‘end’, inline: ‘nearest’}
当未传入参数时,默认值为:{behavior: ‘auto’, block: ‘start’, inline: ‘nearest’}
scrollIntoViewOptions:一个包含下列属性的对象。
behavior定义过渡动画,默认值为auto。
auto,表示没有平滑的滚动动画效果。
smooth,表示有平滑的滚动动画效果。
block定义垂直方向的对齐,默认值为start。
start,表示顶端对齐。
center,表示中间对齐。
end,表示底端对齐。
nearest:如果元素完全在视口内,则垂直方向不发生滚动。
注:如果元素未能完全在视口内,则根据最短滚动距离原则,垂直方向滚动父级容器,使元素完全在视口内。
inline定义水平方向的对齐,默认值为nearest。
start,表示左端对齐。
center,表示中间对齐。
end,表示右端对齐。
nearest:如果元素完全在视口内,则水平方向不发生滚动。
注:如果元素未能完全在视口内,则根据最短滚动距离原则,水平方向滚动父级容器,使元素完全在视口内。
<template>
<div class="box">
<div class="left">
<p @click="test('id1')">第一段内容</p>
<p @click="test('id2')">第二段内容</p>
</div>
<div class="right">
<div class="content" id="id1">我是第一段内容</div>
<div class="content" id="id2">我是第二段内容</div>
</div>
</div>
</template>
<script setup lang="ts">
const test = (data: any) => {
document?.getElementById(data)?.scrollIntoView({
behavior: "smooth", //smooth:平滑,auto:直接定位
block: "start",
inline: "start",
});
};
</script>
<style scoped lang="scss">
.box {
width: 50vw;
height: 50vh;
display: flex;
overflow: hidden;
border: 1px solid gray;
.left {
width: 100px;
height: 100%;
border-right: 2px solid gray;
p {
height: 40px;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border: 1px solid #ccc;
}
}
.right {
flex: auto;
overflow: auto;
.content {
width: 100%;
height: 1000px;
border-top: 2px solid blue;
padding: 20px;
box-sizing: border-box;
}
}
}
</style>
<template>
<button @click="scrollToContact">Go to Contact</button>
<div ref="contact">Contact Section</div>
</template>
<script>
export default {
methods: {
scrollToContact() {
this.$refs.contact.scrollIntoView({ behavior: 'smooth' });
}
}
}
</script>
在这个例子中,当按钮被点击时,页面会平滑滚动到 "Contact Section" 这个元素。
解决方案3:
你也可以使用 window.scrollTo
方法来实现页面的平滑滚动。
methods: {
scrollToTop() {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
}
}
在这个例子中,当调用 scrollToTop
方法时,页面会平滑滚动到页面顶部。
注意:behavior: 'smooth'
选项只有在 window.scrollTo
,Element.scrollIntoView()
和 scrollTo
方法中有效。如果你尝试在其他地方使用它,例如在 window.scrollBy
中,它将不会生效。
延迟滚动
有时候我们不希望立即执行滚动行为。例如当页面做了过渡动效,我们希望过渡结束后再执行滚动。要做到这一点,我们可以返回一个 Promise
:
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ left: 0, top: 0 })
}, 500)
})
}
})