Loading... # 前言 一个问题:**如何让用户在浏览信息时,点击进入详情页面后再返回后仍然保持当前的浏览进度位置不变。** # 正文 不做处理的话页面会重新获取数据渲染,也就是说用户会回到初始状态,这样对于用户浏览的体验并不友好,因此,有以下方法可以解决这个问题,优化用户体验。 ## 方法总结 在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); }); } }); ``` 最后修改:2024 年 06 月 04 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请随意赞赏