如何在Nuxt 3中创建带URL索引的灯箱模态框且打开时不丢失滚动位置
Nuxt模态框打开时父页面滚动位置重置问题
核心冲突需求
- 打开模态框时保持父页面滚动位置不变
- 打开模态框时修改URL(支持通过URL直接进入模态框状态)
当前采用pages目录自动生成嵌套路由的方案,模态框功能正常,但存在一个问题:打开模态框时父页面的滚动位置会被重置到顶部;关闭模态框时滚动位置能正常保留(符合预期)。
问题复现步骤
- 将
/pages/index.vue中的列表项复制约50次,让页面出现滚动条 - 访问根路径
/,向下滚动页面后点击任意Thing链接 - 模态框弹出,但父页面滚动位置被重置到顶部
- 模态框打开状态下滚动页面,实际滚动的是父页面
- 点击模态框的关闭按钮,父页面滚动位置保留(符合预期)
相关代码
/pages/index.vue
<template> <ol> <li><NuxtLink :href="'/session/1'">Thing 1</NuxtLink></li> <li><NuxtLink :href="'/session/2'">Thing 2</NuxtLink></li> <li><NuxtLink :href="'/session/3'">Thing 3</NuxtLink></li> <li><NuxtLink :href="'/session/4'">Thing 4</NuxtLink></li> <li><NuxtLink :href="'/session/5'">Thing 5</NuxtLink></li> <!-- 复制多次直至页面出现滚动条 --> </ol> <LazyNuxtPage /> </template>
/pages/index/session/[id].vue
<script setup lang="ts"> import { useRoute, useRouter } from 'vue-router'; const route = useRoute(); const router = useRouter(); </script> <template> <div :style="{ position: 'fixed', top: '16px', right: '16px', backgroundColor: '#cccccc', padding: '8px' }"> Thing {{ route.params.id }}. <button @click="router.push('/')">(Close overlay)</button> </div> </template>
解决方案
方法1:自定义路由滚动行为(推荐)
在nuxt.config.ts中配置scrollBehavior,针对模态框路由特殊处理,保留父页面滚动位置:
export default defineNuxtConfig({ router: { scrollBehavior(to, from, savedPosition) { // 从根页面进入模态框路由时,保留当前滚动位置 if (from.path === '/' && to.path.startsWith('/session/')) { return { left: window.scrollX, top: window.scrollY }; } // 其他场景遵循默认行为,关闭模态框时用savedPosition恢复 return savedPosition || { top: 0 }; }, }, });
方法2:父页面手动保存/恢复滚动位置
在index.vue中监听路由变化,打开模态框前保存滚动位置,必要时恢复:
<template> <ol> <li><NuxtLink :href="'/session/1'">Thing 1</NuxtLink></li> <!-- 其他列表项 --> </ol> <LazyNuxtPage /> </template> <script setup lang="ts"> import { useRoute } from 'vue-router'; import { ref, watch } from 'vue'; const route = useRoute(); const savedScrollTop = ref(0); // 进入模态框路由时保存当前滚动位置 watch(() => route.path, (newPath) => { if (newPath.startsWith('/session/')) { savedScrollTop.value = window.scrollY; } }); // 直接访问模态框URL后返回根页面时,恢复滚动位置 watch(() => route.path, (newPath) => { if (newPath === '/' && savedScrollTop.value > 0) { // 延迟执行确保DOM已更新 setTimeout(() => window.scrollTo(0, savedScrollTop.value), 0); } }); </script>
方法3:模态框页面主动恢复滚动
在模态框页面挂载时,恢复之前保存的滚动位置(需配合父页面保存逻辑):
<script setup lang="ts"> import { useRoute, useRouter } from 'vue-router'; import { onMounted } from 'vue'; const route = useRoute(); const router = useRouter(); onMounted(() => { // 读取父页面保存的滚动位置并恢复 const scrollTop = sessionStorage.getItem('parentScrollTop'); if (scrollTop) { window.scrollTo(0, parseInt(scrollTop)); sessionStorage.removeItem('parentScrollTop'); } }); </script> <template> <div :style="{ position: 'fixed', top: '16px', right: '16px', backgroundColor: '#cccccc', padding: '8px' }"> Thing {{ route.params.id }}. <button @click="router.push('/')">(Close overlay)</button> </div> </template>
同时在父页面的NuxtLink点击事件中保存滚动位置:
<!-- 在index.vue的列表项中修改 --> <li> <NuxtLink :href="'/session/1'" @click="sessionStorage.setItem('parentScrollTop', window.scrollY.toString())" > Thing 1 </NuxtLink> </li>
内容的提问来源于stack exchange,提问作者Jessica Knight
相关产品推荐
相关产品推荐

