前言

一个问题:如何让用户在浏览信息时,点击进入详情页面后再返回后仍然保持当前的浏览进度位置不变。

正文

不做处理的话页面会重新获取数据渲染,也就是说用户会回到初始状态,这样对于用户浏览的体验并不友好,因此,有以下方法可以解决这个问题,优化用户体验。

方法总结

在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 日
如果觉得我的文章对你有用,请随意赞赏