fix(template-07): 移动端顶部菜单展开后无法滚动
菜单面板留在 sticky 的 <header> 内,踩了两个叠加的 CSS 陷阱: 1) <header> 带 sticky(position:sticky + z-50)会自建层叠上下文, 面板在其中 z 写多大都只能在 header 内部排序,整体仍被 DOM 靠后的 <main>(Hero 为 relative,同样自建上下文)盖住。 2) sticky 的 header 会成为后代 fixed 的包含块——fixed 定位基准从视口 退化成 header 的盒子并被其高度裁剪。原实现面板是普通流式元素,高度 由内容撑开(汇吉采 6 个带子菜单的栏目约 1000px)直接溢出屏幕,而 header 内没有可滚动容器,于是「展开即滚不动」,底部栏目够不着。 修复:面板 Teleport 到 body 脱离 header 的层叠上下文;面板改 fixed 并 四边锚定(inset-x-0 top-16 bottom-0 + overflow-y-auto + overscroll-contain), top/bottom 同时钉住后高度才由视口决定,长菜单在面板内滚动。 实测(390x844 设备模拟):面板可滚到底,header 固定、背景页不动; 桌面 1440x900 无回归。约束与「只写 top-16 不够」的警告已写进组件注释。 Refs: .scratch/hjc-web-mobile-menu-scroll/issues/01-fix-mobile-menu-scroll.md
This commit is contained in:
@@ -88,48 +88,50 @@
|
||||
</div>
|
||||
|
||||
<!-- 移动端菜单 -->
|
||||
<div
|
||||
v-if="mobileMenuOpen"
|
||||
class="lg:hidden bg-white border-t border-gray-100"
|
||||
>
|
||||
<div class="container mx-auto px-4 py-4 space-y-1">
|
||||
<template v-for="item in navItems" :key="item.navigationId || item.path">
|
||||
<!-- 有子菜单 -->
|
||||
<div v-if="item.children && item.children.length > 0">
|
||||
<div class="px-4 py-3 text-base font-semibold text-gray-900">
|
||||
{{ item.title }}
|
||||
<Teleport to="body">
|
||||
<div
|
||||
v-if="mobileMenuOpen"
|
||||
class="fixed inset-x-0 top-16 bottom-0 z-[60] overflow-y-auto overscroll-contain lg:hidden bg-white border-t border-gray-100"
|
||||
>
|
||||
<div class="container mx-auto px-4 py-4 space-y-1">
|
||||
<template v-for="item in navItems" :key="item.navigationId || item.path">
|
||||
<!-- 有子菜单 -->
|
||||
<div v-if="item.children && item.children.length > 0">
|
||||
<div class="px-4 py-3 text-base font-semibold text-gray-900">
|
||||
{{ item.title }}
|
||||
</div>
|
||||
<NuxtLink
|
||||
v-for="child in item.children"
|
||||
:key="child.navigationId"
|
||||
:to="getChildPath(child)"
|
||||
class="block px-8 py-2 text-sm text-gray-600 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
|
||||
@click="mobileMenuOpen = false"
|
||||
>
|
||||
{{ child.title }}
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<!-- 无子菜单 -->
|
||||
<NuxtLink
|
||||
v-for="child in item.children"
|
||||
:key="child.navigationId"
|
||||
:to="getChildPath(child)"
|
||||
class="block px-8 py-2 text-sm text-gray-600 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
|
||||
v-else
|
||||
:to="getNavLink(item)"
|
||||
:target="item.target === '_blank' ? '_blank' : undefined"
|
||||
class="block px-4 py-3 text-base font-medium text-gray-700 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
|
||||
active-class="text-blue-600 bg-blue-50"
|
||||
@click="mobileMenuOpen = false"
|
||||
>
|
||||
{{ child.title }}
|
||||
{{ item.title }}
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<!-- 无子菜单 -->
|
||||
</template>
|
||||
<NuxtLink
|
||||
v-else
|
||||
:to="getNavLink(item)"
|
||||
:target="item.target === '_blank' ? '_blank' : undefined"
|
||||
class="block px-4 py-3 text-base font-medium text-gray-700 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
|
||||
active-class="text-blue-600 bg-blue-50"
|
||||
to="/contact"
|
||||
class="block px-4 py-3 mt-2 text-center bg-blue-600 text-white font-semibold rounded-lg hover:bg-blue-700 transition-colors"
|
||||
@click="mobileMenuOpen = false"
|
||||
>
|
||||
{{ item.title }}
|
||||
在线咨询
|
||||
</NuxtLink>
|
||||
</template>
|
||||
<NuxtLink
|
||||
to="/contact"
|
||||
class="block px-4 py-3 mt-2 text-center bg-blue-600 text-white font-semibold rounded-lg hover:bg-blue-700 transition-colors"
|
||||
@click="mobileMenuOpen = false"
|
||||
>
|
||||
在线咨询
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
@@ -143,6 +145,23 @@ import type { CmsNavigation } from '~/types'
|
||||
const { siteInfo, siteName, siteLogo, siteIcon, navigations, showHeaderSearch, fetchSiteInfo } = useSite()
|
||||
const mobileMenuOpen = ref(false)
|
||||
|
||||
/*
|
||||
* 移动端菜单(模板里 `v-if="mobileMenuOpen"` 那块)为什么长这样——两个坑都踩过,别改回去:
|
||||
*
|
||||
* 1) 必须 <Teleport to="body">,不能留在 <header> 内。
|
||||
* <header> 带 `sticky`(position:sticky + z-50),这会让它**自建层叠上下文**;
|
||||
* 面板留在里面时,z 写多大都只在 header 内部排序,整体仍被 DOM 靠后的
|
||||
* <main>(Hero 是 relative,同样自建上下文)盖住 → 菜单"展开了却看不见"。
|
||||
*
|
||||
* 2) 面板必须是 `fixed` 且四边锚定(inset-x-0 top-16 bottom-0 + overflow-y-auto)。
|
||||
* <header> 是 sticky 时会成为后代 fixed 的**包含块**,fixed 的定位基准从视口
|
||||
* 退化成 header 的盒子并被其高度裁剪。菜单项多起来时(汇吉采 6 个带子菜单的栏目,
|
||||
* 展开约 1000px)面板会撑出屏幕,而 header 里没有可滚动容器 →
|
||||
* 「展开即滚不动」,底部栏目在任何机型上都够不着(原缺陷)。
|
||||
* top/bottom 同时钉住后高度才由视口决定,长菜单才能在面板内滚动。
|
||||
* ⚠️ 只写 `top-16` 不够:高度仍锁在 header 的 h-16 内,症状原样复现。
|
||||
*/
|
||||
|
||||
/** 子导航链接统一走 app/utils 的 getNavLink(自动避免 /page/4598?navId=4598 冗余拼接) */
|
||||
function getChildPath(child: CmsNavigation): string {
|
||||
return getNavLink(child)
|
||||
|
||||
Reference in New Issue
Block a user