WordPress函数:wp_nav_menu 显示导航菜单

编辑文章

简介

wp_nav_menu() 是 WordPress 主题中用于在页面上渲染一个已创建菜单的核心函数。你可以把它想象成一个”菜单打印机”——只要告诉它使用哪个菜单(或哪个位置),它就会自动从数据库中读取菜单项,并输出结构完整、带有标准CSS类的HTML列表代码,让你能够轻松地通过CSS来设计样式。

语法

此函数定义于 wp-includes/nav-menu-template.php 文件中。

/**
 * 显示一个导航菜单。
 *
 * @param array $args {
 *     用于自定义菜单输出的参数数组。
 *
 *     @type int|string|WP_Term $menu            要显示的菜单。可以是菜单ID、slug、名称或对象。默认:无。
 *     @type string             $menu_class      用于构成菜单的<ul>元素的CSS类。默认:'menu'。
 *     @type string             $menu_id         用于菜单的<ul>元素的ID。默认:菜单slug的别名。
 *     @type string             $container       是否用容器包装ul元素,以及使用何种标签。默认:'div'。
 *     @type string             $container_class 容器元素使用的CSS类。默认:'menu-{menu slug}-container'。
 *     @type string             $container_id    容器元素使用的ID。默认:无。
 *     @type bool               $fallback_cb     如果菜单不存在,使用的回调函数。默认:'wp_page_menu'。
 *     @type string             $before          链接文本前输出的文本。默认:无。
 *     @type string             $after           链接文本后输出的文本。默认:无。
 *     @type string             $link_before     链接文本前输出的HTML。默认:无。
 *     @type string             $link_after      链接文本后输出的HTML。默认:无。
 *     @type bool               $echo            是否直接回显菜单或返回菜单HTML。默认:true。
 *     @type int                $depth           菜单的深度。默认:0(全部)。
 *     @type object             $walker          自定义walker类实例。默认:无。
 *     @type string             $theme_location  要使用的主题位置。默认:无。
 *     @type array              $items_wrap      如何包装列表项。默认:'<ul id=\"%1$s\" class=\"%2$s\">%3$s</ul>'。
 *     @type string             $item_spacing    是否保留列表项中的空白。默认:'preserve' 或 'discard'。
 * }
 * @return string|void|false 如果 $echo 为 false,则返回菜单HTML。默认无返回值。
 */
function wp_nav_menu( $args = array() ) {
    // ... 函数内部实现
}

用法

基础用法

最基础的用法是在主题模板文件(如 header.php)中调用此函数,并指定一个已在 functions.php 中通过 register_nav_menus() 注册的主题位置

<!-- 在主题的 header.php 文件中,导航区域 -->
<header class="site-header">
    <nav class="main-navigation">
        <?php
        wp_nav_menu(
            array(
                'theme_location' => 'menu-primary', // 必须!指定菜单显示位置
                'menu_class'     => 'primary-menu', // 为 <ul> 标签添加的 class
                'container'      => false, // 不生成额外的容器 div
                'fallback_cb'    => false, // 如果此位置无菜单,则什么都不显示
            )
        );
        ?>
    </nav>
</header>

通过 theme_location 调用,可以将菜单的逻辑定义(在后台分配哪个菜单)与主题模板解耦。网站管理员可以在后台自由更换菜单内容,而无需修改主题代码。设置 'container' => false 是为了获得更简洁、更易控制的HTML结构,方便现代CSS布局。

进阶用法

场景:在页脚显示一个自定义菜单,并为其添加独特的样式类,同时限制只显示一级菜单。

<!-- 在主题的 footer.php 文件中 -->
<footer class="site-footer">
    <?php
    wp_nav_menu(
        array(
            'theme_location'  => 'menu-footer',
            'menu_class'      => 'footer-menu clearfix', // 可以添加多个类
            'container_class' => 'footer-menu-container',
            'depth'           => 1, // 关键:只显示1级深度,不显示子菜单
            'fallback_cb'     => 'wp_page_menu', // 无自定义菜单时,显示页面列表
            'items_wrap'      => '<ul id=\"%1$s\" class=\"%2$s\"><li class=\"footer-home\"><a href=\"' . esc_url( home_url( '/' ) ) . '\">Home</a></li>%3$s</ul>', // 自定义包裹HTML,并手动添加一个首页项
        )
    );
    ?>
</footer>

depth 参数对于控制菜单层级非常有用,在页脚等空间有限的区域,通常只需要一级菜单。items_wrap 提供了极高的灵活性,允许你在菜单列表的开头或结尾插入自定义的HTML项(如上例中的”Home”),而无需为此创建一个单独的菜单项或修改Walker类。

场景:通过 Walker 类实现高度定制化

当默认的HTML结构无法满足设计需求时(例如,需要为不同层级的菜单项添加复杂的包裹元素、图标或数据属性),可以创建自定义的 Walker_Nav_Menu 类。这是最强大但也最复杂的定制方式。

// 1. 在 functions.php 中定义自定义 Walker 类
class My_Custom_Menu_Walker extends Walker_Nav_Menu {
    // 重写 start_lvl, start_el, end_el, end_lvl 等方法来自定义输出
    function start_el( &$output, $item, $depth = 0, $args = null, $id = 0 ) {
        // 你的自定义逻辑,例如添加 data-* 属性
        $output .= sprintf(
            '<li id="menu-item-%d" class="%s" data-custom-data="%s">',
            $item->ID,
            implode( ' ', $item->classes ),
            esc_attr( $item->description ) // 使用描述字段作为自定义数据
        );
        // … 继续生成链接等
    }
}

// 2. 在调用 wp_nav_menu 时使用这个 Walker
wp_nav_menu( array(
    'theme_location' => 'menu-primary',
    'walker'         => new My_Custom_Menu_Walker(), // 传入Walker实例
) );

为什么及何时使用? 当设计稿要求与WordPress默认菜单HTML结构差异巨大时使用。它提供了像素级控制能力,但增加了代码复杂度和维护成本,应优先考虑用CSS能否解决。

易错点

混淆 theme_locationmenu 参数

❌ 错误示例:

<?php
// 假设在后台创建了一个名为"Main Nav"的菜单
wp_nav_menu( array( 'menu' => 'menu-primary' ) ); // 错误:把'位置标识符'传给了'menu'参数
?>

原因theme_location 参数用于调用在主题中注册的菜单位置,该位置可以由管理员在”外观 > 菜单”的”显示位置”选项卡中分配任意菜单。而 menu 参数则用于通过ID、slug或名称直接调用一个特定的菜单,无视主题位置。两者用途不同,混淆会导致函数找不到指定内容而不显示菜单。

✅ 正确示例:

<?php
// 方式一:通过主题位置调用(推荐,更灵活)
wp_nav_menu( array( 'theme_location' => 'menu-primary' ) );

// 方式二:通过菜单slug直接调用(固定,不灵活)
wp_nav_menu( array( 'menu' => 'main-nav' ) );
?>

未处理菜单项标题中的特殊字符,导致HTML结构破坏

❌ 错误示例:

<?php
// 假设一个菜单项标题是 "Café & Bar <3"
wp_nav_menu();
// 输出的HTML可能变成:<a href="...">Café & Bar <3</a>, 其中的 & 和 < 会破坏HTML。
?>

原因:菜单项的标题是用户输入的文本,可能包含与HTML语法冲突的字符(如 &, <, >)。虽然 wp_nav_menu() 在最终输出链接标题时,核心代码会调用 esc_html() 进行转义,但最佳实践是确保在保存菜单项时就已处理。更重要的是,对于 link_beforelink_after 等参数中开发者自定义的HTML,必须自行转义。

✅ 正确示例:

<?php
// 使用 link_before/link_after 添加图标时,确保图标HTML是安全的
wp_nav_menu( array(
    'theme_location' => 'menu-primary',
    'link_before'    => '<span class="menu-icon">', // 静态HTML,安全
    'link_after'     => '</span>',
) );
?>

更根本的预防:教育内容编辑者在输入菜单标题时避免直接使用特殊字符,或使用HTML实体(如 &, <)。

最佳实践

利用缓存提升性能

对于结构复杂、菜单项众多且更新不频繁的菜单,可以启用对象缓存来避免每次页面加载都执行数据库查询。

// 使用 'wp_nav_menu' 过滤器在菜单创建前检查缓存
add_filter( 'pre_wp_nav_menu', 'mytheme_get_cached_menu', 10, 2 );
function mytheme_get_cached_menu( $null, $args ) {
    // 创建一个唯一的缓存键,基于所有参数
    $key = 'mytheme_menu_' . md5( serialize( $args ) );
    $cached_menu = get_transient( $key );

    if ( $cached_menu !== false ) {
        return $cached_menu; // 返回缓存的HTML
    }
    return $null; // 无缓存,继续正常生成
}

// 使用 'wp_nav_menu' 过滤器在菜单生成后设置缓存
add_filter( 'wp_nav_menu', 'mytheme_set_cached_menu', 10, 2 );
function mytheme_set_cached_menu( $nav_menu, $args ) {
    $key = 'mytheme_menu_' . md5( serialize( $args ) );
    // 缓存12小时
    set_transient( $key, $nav_menu, 12 * HOUR_IN_SECONDS );
    return $nav_menu;
}

对于高流量网站,缓存菜单可以显著降低数据库负载,加快页面响应速度。注意:需要在菜单更新时(如 wp_update_nav_menu 动作)清理相关缓存,以确保内容及时更新。

与现代区块主题(FSE)的结合

在全站编辑(FSE)区块主题中,导航菜单的创建和渲染方式发生了根本性变化。它不再通过在 header.php 模板文件中调用 wp_nav_menu() 来实现。

  • 区块主题的方式:导航由”导航”区块(Navigation Block)在站点编辑器(Site Editor)中可视化的创建和放置。其样式通过主题的 theme.json 文件和区块样式工具进行全局控制。
  • 传统主题的演进:即使在使用 wp_nav_menu() 的传统主题中,也应考虑为其添加对”导航”区块的部分支持,以提升用户体验的一致性。例如,确保菜单输出的HTML结构能够被区块编辑器的样式系统较好地识别和适配。

确保可访问性

生成的菜单HTML应遵循无障碍网页倡议(WCAG)标准。wp_nav_menu() 默认输出已包含一些可访问性特性(如基本的ARIA角色)。开发者应:
– 避免移除或破坏默认的 role="navigation"aria-label 等属性。
– 在使用自定义Walker时,确保为交互元素(如下拉菜单)添加正确的 aria-expandedaria-controls 等状态属性。
– 测试菜单可以通过键盘(Tab键)完全导航。