前言
一个问题:如何让用户在浏览信息时,点击进入详情页面后再返回后仍然保持当前的浏览进度位置不变。
正文
不做处理的话页面会重新获取数据渲染,也就是说用户会回到初始状态,这样对于用户浏览的体验并不友好,因此,有以下方法可以解决这个问题,优化用户体验。
方法总结
在vue3中在路由配置中设置页面保持缓存以及滚动位置信息的元数据,在组件中利用 onActivated
钩子在组件激活时从路由元信息中获取存储的滚动位置恢复状态
在原生js中,方法思路都是一样的,监听页面的加载与卸载事件,保存滚动位置到内存中以及恢复滚动位置
示例代码
vue3
首先,构建一个信息流组件,有一个列表,通过 v-for
循环渲染通过 axios
获取到的数据项,并为添加点击事件以跳转到详情页。
<ul>
<li
class="list"
v-for="item in data"
:key="item.id"
@click="goToDetail(item)"
>
<p class="card">{{ item.id }}</p>
</li>
</ul>
import axios from 'axios';
import { onBeforeMount, onActivated } from 'vue';
import { useRoute, useRouter } from 'vue-router';
const router = useRouter();
const route = useRoute();
import { ref } from 'vue';
let data = ref();
let scrollTop = ref(0);
// 获取数据
async function fetchData() {
const response = await axios.get('./data.json');
data.value = response.data;
}
// 跳转到详情页
function goToDetail(item) {
router.push({
name: 'info',
params: { id: item.id.toString() },
query: { info: item.info },
});
}
// 缓存的位置
onActivated(() => {
const $content = document.querySelector('#app');
// 从路由元信息中获取存储的滚动位置
scrollTop.value = route.meta.scrollTop || 0;
$content.scrollTop = scrollTop.value;
});
onBeforeMount(() => {
fetchData();
});
构建详情页组件
<template>
<div class="info">
<p>id: {{ id }}</p>
<p>info: {{ info }}</p>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { useRoute } from 'vue-router';
const route = useRoute();
const id = computed(() => route.params.id);
const info = computed(() => route.query.info as string);
</script>
<style lang="scss" scoped></style>
在 App.vue
中判断是否是需要保留状态的组件
<template>
<div class="app">
<router-view v-slot="{ Component, route }">
<keep-alive v-if="route.meta.keepAlive">
<component :is="Component" />
</keep-alive>
<component v-else :is="Component" />
</router-view>
</div>
</template>
路由中设置 keepAlive
以及 scrollTop
并且更新路由元数据中的 scrollTop
import { createRouter, createWebHistory } from 'vue-router';
import Home from '@/components/Home.vue';
import Info from '@/components/Info.vue';
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
component: Home,
meta: {
keepAlive: true,
scrollTop: 0,
},
},
{
path: '/info/:id',
name: 'info',
component: Info,
meta: {
keepAlive: true,
},
},
],
});
router.beforeEach((to, from, next) => {
// 记录缓存页之前的位置,从详情页返回后可以直接跳转回当前位置
if (from.meta && from.meta.keepAlive) {
const $content = document.getElementById('app');
const scrollTop = $content ? $content.scrollTop : 0;
// 更新 from 路由的元数据中的 scrollTop 值
from.meta.scrollTop = scrollTop;
}
next();
});
export default router;
原生js
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<ul id="data-list"></ul>
<script src="index.js"></script>
<style>
body {
font-size: 12px;
}
/* 样式表 */
#data-list {
list-style-type: none;
padding: 0;
}
#data-list li {
padding: 10px;
height: 20px;
border-bottom: 1px solid #ccc;
}
#data-list li:hover {
background-color: #f9f9f9;
}
</style>
</body>
</html>
detail.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Detail Page</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<p id="details"></p>
<script>
// 监听 DOM 加载完成
document.addEventListener('DOMContentLoaded', function () {
// 获取查询字符串参数
const params = new URLSearchParams(window.location.search);
// 获取名为 'itemId' 的参数
const itemId = params.get('itemId');
// 检查 itemId 是否存在
if (itemId) {
// 将获取到的 itemId 设置到页面元素中
document.getElementById('details').textContent = 'ID: ' + itemId;
} else {
document.getElementById('details').textContent = 'no ID';
}
});
</script>
</body>
</html>
index.js
document.addEventListener('DOMContentLoaded', function () {
const dataList = document.getElementById('data-list');
// 保存滚动位置
let savedScrollPosition = 0;
// 页面加载时自动获取数据
fetchDataAndRenderList();
window.onbeforeunload = function () {
// 保存当前滚动位置
savedScrollPosition =
window.scrollY ||
window.pageYOffset ||
document.documentElement.scrollTop;
};
window.onload = function () {
// 恢复滚动位置
if (savedScrollPosition) {
window.scrollTo(0, savedScrollPosition);
}
};
function fetchDataAndRenderList() {
fetch('./data.json')
.then((response) => response.json())
.then((data) => renderList(data))
.catch((error) => console.error('error', error));
}
function renderList(data) {
// 清空现有列表
dataList.innerHTML = '';
// 渲染新数据到列表
data.forEach((item) => {
const listItem = document.createElement('li');
// 确保文本内容是字符串
listItem.textContent = item.id || '未知内容';
listItem.addEventListener('click', function () {
// 保存当前滚动位置并打开详情页
savedScrollPosition =
window.scrollY ||
window.pageYOffset ||
document.documentElement.scrollTop;
// item.id 详情页参数
window.open('detail.html?itemId=' + item.id, '_self');
});
dataList.appendChild(listItem);
});
}
});