本文约 4,329 字,预计阅读 11 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:三个菜单问题,几乎每个站都会遇到一遍
第一,主题文档说支持”主导航”,你在后台建好了菜单、勾了位置,前台却纹丝不动;第二,二级菜单建好了,鼠标移上去就是不展开;第三,电脑上一切正常,手机上汉堡按钮点了完全没反应,或者菜单展开后盖住了内容、点不动链接。
这三个问题的成因完全不同:第一个是”菜单位置”没对上,第二个多半是 CSS 与菜单层级不匹配,第三个通常是 JS 依赖或层叠上下文的问题。lumen-mimangfei 主题的导航由 register_nav_menus() 注册位置、wp_nav_menu() 输出,弄清楚这条链路,问题就好定位了。
原理分析:从注册到输出的完整链路
菜单位置(theme_location)由主题在 after_setup_theme 里用 register_nav_menus() 声明,只是”占位符”,本身不包含任何菜单项。菜单项数据存在数据库里:菜单本身是 nav_menu 分类法下的 term,菜单项是 nav_menu_item 类型的 post,两者通过 wp_term_relationships 关联。
输出时 wp_nav_menu() 根据 theme_location 找到被分配的菜单,取菜单项后用 Walker_Nav_Menu 递归生成 <ul><li> 结构。depth 参数控制递归层数,但层级上限还受主题 CSS 约束——很多主题只写了 .sub-menu 两级样式,你把 depth 放开也看不到第三级。
移动端菜单通常不是 PHP 的问题,而是缺一段切换逻辑:主题输出一个按钮和一个容器,靠 JS 切换 class,再用 CSS 控制显示。若这段 JS 在 jQuery 之前加载,或者被缓存插件合并压缩出错,点击就没反应。
| 现象 | 大概率原因 |
|---|---|
| 菜单完全不显示 | theme_location 拼写不一致 / 该位置未分配菜单 |
| 显示成所有页面列表 | 未分配菜单,走了 fallback_cb 的 wp_page_menu |
| 二级菜单不展开 | 缺少 .sub-menu 的定位与 hover/focus 样式 |
| 移动端点击无反应 | 切换 JS 未加载、报错,或被缓存的旧版本覆盖 |
| 登录后菜单不更新 | 页面缓存把未登录态的 HTML 缓存了 |
保姆级实操
- 确认菜单位置名称:在主题里搜索
register_nav_menus,记下数组键名(如primary、footer),这是theme_location要填的值,不是显示名。 - 后台建菜单并分配:外观 → 菜单,新建菜单后务必在”显示位置”勾选对应位置并保存。
- 建二级菜单:在菜单结构里把子项向右拖动一格即可,WordPress 本身不限制层级。
- 输出时显式声明参数:把
container、menu_class、depth、fallback_cb写清楚,尤其fallback_cb设成false可以避免”未分配时静默显示页面列表”。 - 移动端加切换逻辑:给导航容器加一个按钮,用原生 JS 切换 aria 属性与 class,不依赖 jQuery,减少出错面。
- 补 CSS:二级菜单用
position:absolute+focus-within保证键盘与触屏可达;移动端用媒体查询切换为抽屉式。 - 清缓存验证:改完菜单先清页面缓存,并在无痕窗口里确认登录/未登录两种状态。
<?php
// 1) 注册菜单位置(注意键名才是 theme_location)
add_action( 'after_setup_theme', 'mimangfei_register_menus' );
function mimangfei_register_menus() {
register_nav_menus( array(
'primary' => __( '主导航', 'lumen-mimangfei' ),
'footer' => __( '页脚导航', 'lumen-mimangfei' ),
) );
}
// 2) 输出主导航,避免未分配时回退成页面列表
wp_nav_menu( array(
'theme_location' => 'primary',
'container' => 'nav',
'container_id' => 'site-navigation',
'menu_class' => 'primary-menu',
'depth' => 2,
'fallback_cb' => false,
) );
<?php
// 给当前菜单项追加 aria-current,方便 CSS 高亮与无障碍
add_filter( 'nav_menu_item_attributes', 'mimangfei_menu_item_attrs', 10, 2 );
function mimangfei_menu_item_attrs( $atts, $item ) {
if ( ! empty( $item->current ) ) {
$atts['aria-current'] = 'page';
}
if ( ! empty( $atts['class'] ) ) {
$atts['class'] .= ' menu-item-custom';
}
return $atts;
}
/* 二级菜单:定位 + hover 与键盘可达 */
.primary-menu .sub-menu {
position: absolute;
left: 0;
top: 100%;
min-width: 180px;
background: #fff;
box-shadow: 0 6px 18px rgba(0, 0, 0, .12);
opacity: 0;
visibility: hidden;
transition: opacity .18s ease;
z-index: 50;
}
.primary-menu li {
position: relative;
}
.primary-menu li:hover > .sub-menu,
.primary-menu li:focus-within > .sub-menu {
opacity: 1;
visibility: visible;
}
/* 移动端抽屉 */
@media ( max-width: 782px ) {
#site-navigation .primary-menu {
display: none;
}
#site-navigation.is-open .primary-menu {
display: block;
}
.primary-menu .sub-menu {
position: static;
opacity: 1;
visibility: visible;
box-shadow: none;
}
}
// 原生 JS 切换,不依赖 jQuery
document.addEventListener( 'DOMContentLoaded', function () {
const nav = document.getElementById( 'site-navigation' );
const btn = document.querySelector( '.menu-toggle' );
if ( ! nav || ! btn ) {
return;
}
btn.addEventListener( 'click', function () {
const open = nav.classList.toggle( 'is-open' );
btn.setAttribute( 'aria-expanded', open ? 'true' : 'false' );
} );
} );
常见踩坑
theme_location填的是显示名(”主导航”)而不是键名(primary),匹配失败。- 忘了在后台”显示位置”勾选,前台走了
fallback_cb默认输出全部页面,看起来像”菜单自己乱了”。 - 二级菜单父项没有
position: relative,子菜单跑到页面左上角。 - 移动端切换 JS 依赖 jQuery 但脚本排队顺序在前,控制台报
$ is not defined。 - 案例:某站改了菜单名称后,前台导航突然变成”首页、关于我们、联系我们……”一长串。原因是菜单被重命名后与位置的绑定丢失,
fallback_cb默认调用wp_page_menu兜底。把fallback_cb设为false并重新分配位置后恢复正常,也避免了以后再出现这种”静默兜底”。 - 菜单展开层被其他元素的
z-index盖住,看起来像没反应。 - 开了页面缓存后,登录用户看到的仍是未登录态菜单(”登录/注册”一直显示)。
总结
排查顺序固定为:位置键名 → 后台是否分配 → depth 与 CSS 是否匹配 → 移动端 JS 是否加载 → 缓存是否干扰。判断标准是”未分配菜单时应当什么都不显示”,把 fallback_cb 设为 false 能立刻暴露问题。行动清单:① 统一位置键名;② 显式声明 depth;③ 移动端用原生 JS 减少依赖;④ 二级菜单加 focus-within;⑤ 缓存插件排除登录用户。相关延伸可看 lumen-mimangfei 主题首页布局、lumen-mimangfei 主题移动端显示问题 与 WordPress 主题自定义 JS 添加。
