前言
移动端有个常见的业务场景,在列表页滑动进入某一个详情页,回退的时候希望列表能够停留在原先访问的位置,本篇便针对这一需求,带大家一起完善我们的列表组件,顺便结合这个列表组件 实现一个多标签列表返回定位 的案例
思路
这一功能的实现网上已有很多相关案例,但大多都使用了监听滚动事件,动态的记录滚动位置,但分析下来这一功能实现实际上并不需要实时监听,只需要在 离开页面时记录滚动位置 ,重新进入页面时赋值就好了
按照这个思路,我们需要完成三步:
- 缓存组件,即切换页面时页面组件不会被销毁
- 记录位置,页面离开时记录滚动条位置
- 重新定位,重新进入页面时定位到之前记录的位置
缓存组件
这里我们可以用之前已经实现的 <app-router-view> 组件来实现,借助 keepAlive 帮助我们实现页面离开时缓存页面的状态
关于 keepAlive 的相关讲解大家可以阅读我的另一篇文章,这里就不赘述了
传送门:5 分钟带你实现一个可控制缓存销毁的 keepAlive 组件
记录位置
这里使用我们之前封装好的列表组件 <app-list> 来进行改造
我们写一个通用方法 useScrollCache 来完成整一块滚动定位缓存这一需求的逻辑,首先我们定义一个 listRef 来获取我们列表盒子的 dom 元素,然后通过路由钩子 onBeforeRouteLeave 当页面离开前,把当前的滚动位置 listRef.value.scrollTop 记录下来,这样就完成了位置记录
<script setup>
import { onBeforeRouteLeave } from 'vue-router'
const { listRef } = useScrollCache()
/**
* @description: 滚动定位缓存
*/
function useScrollCache() {
const listRef = ref(null)
let scrollTop = 0
onBeforeRouteLeave(() => {
scrollTop = listRef.value.scrollTop
})
return {
listRef
}
}
</script>
<template>
<div class="app-list" ref="listRef">
<van-pull-refresh
v-model="refreshing"
:success-text="successText"
@refresh="onRefresh"
>
<van-list
v-model:loading="loading"
@load="onLoad"
:finished="finished"
:finished-text="finishedText"
:offset="100"
>
<slot name="content" :list="list"> </slot>
</van-list>
</van-pull-refresh>
</div>
</template>
<style lang="scss" scoped>
.app-list {
height: 100%;
overflow-y: auto;
}
</style>
重新定位
因为我们使用了keepAlive,当我们离开重新进入页面时,会触发 onActivated 生命周期,我们只要在这里把之前记录的位置重新赋值给列表就可以了
完整代码
<script setup>
import { onBeforeRouteLeave } from 'vue-router'
const { listRef } = useScrollCache()
/**
* @description: 滚动定位缓存
*/
function useScrollCache() {
const listRef = ref(null)
let scrollTop = 0
onActivated(() => {
// 激活时重新定位
listRef.value.scrollTop = scrollTop
})
onBeforeRouteLeave(() => {
// 离开记录位置
scrollTop = listRef.value.scrollTop
})
return {
listRef
}
}
</script>
这样,我们一个简单的列表返回记录位置的功能就实现啦,来看看效果
做一个多 Tab 列表切换案例
现在来一起使用我们封装的组件实现一个多页签列表的案例
我们使用 <van-tab> 来做多标签,使用 setTimeout 来模拟数据请求,这里需要注意一点需要把 :animated="true" 设置一下,由于不开启动画效果的话,<app-list> 实例在切换时会被销毁掉,这样处理起来逻辑就变复杂了,这里我们使用动画过度,操作体验也会更友好一点
<script setup>
// This starter template is using Vue 3 <script setup> SFCs
const options1 = {
getData: () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
data: new Array(20),
total: 30
})
}, 1000)
})
}
}
const options2 = {
getData: () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
data: new Array(20),
total: 30
})
}, 1000)
})
}
}
const activeName = ref('a')
</script>
<template>
<app-page>
<van-tabs
class="app-tab-list"
v-model:active="activeName"
:animated="true"
>
<van-tab title="标签 1" name="a">
<app-list :options="options1">
<template #content="{ list }">
<van-cell
v-for="(item, index) in list"
:key="index"
:title="index"
/>
</template>
</app-list>
</van-tab>
<van-tab title="标签 2" name="b">
<app-list :options="options2">
<template #content="{ list }">
<van-cell
v-for="(item, index) in list"
:key="index"
:title="index"
/>
</template>
</app-list>
</van-tab>
<van-tab title="标签 3" name="c"> c </van-tab>
</van-tabs>
</app-page>
</template>
大家来一起看看效果
好了,本篇的内容比较简单也比较实用,希望能对大家完成需求有所帮助,下一篇,我们将对 Vue 自定义指令相关的知识点做讲解,并带大家一起实现一些移动端常用的指令,例如防抖截流,长按事件监听,点击复制文本等等,敬请期待!