分析 WordPress `Walker_Nav_Menu` 类的源码:如何通过继承和重写方法自定义导航菜单的 HTML 结构。

各位观众老爷,大家好!我是你们的老朋友,今天咱们来聊聊WordPress的菜单定制这事儿。别以为菜单就是简简单单的链接列表,它可是网站的门面担当,设计得好能让用户眼前一亮,用得顺手,直接提升用户体验。

今天咱们就来深入剖析一下Walker_Nav_Menu这个类,看看怎么通过继承和重写它的方法,打造出独一无二的导航菜单。

一、 Walker_Nav_Menu 是个啥?

首先,Walker_Nav_Menu是WordPress核心类,它负责把你在后台设置的菜单项转换成HTML代码。简单来说,就是个“翻译官”,把数据翻译成浏览器看得懂的语言。它使用了一种叫“Walker”的设计模式,像一个辛勤的园丁,一层一层地遍历菜单树,然后根据每个菜单项的属性,生成对应的HTML标签。

二、 为什么要自定义?

WordPress默认的菜单结构虽然够用,但有时候不能满足我们的需求。比如:

  • 想要给菜单项添加自定义的class,方便CSS样式控制。
  • 想要在菜单项中插入图片或者其他HTML元素。
  • 想要改变菜单项的标签结构,比如把<a>标签放到<li>标签里面。
  • 想要实现更复杂的菜单效果,比如下拉菜单,Mega Menu等。

总之,想要菜单长成你想要的样子,就得学会自定义。

三、 继承和重写,定制菜单的利器

自定义菜单的关键在于继承Walker_Nav_Menu类,然后重写它的几个核心方法。就像学武功一样,先学会基础招式,然后才能在此基础上创造自己的绝招。

1. 创建自定义的Walker类

首先,我们要创建一个新的类,继承自Walker_Nav_Menu

class My_Custom_Menu_Walker extends Walker_Nav_Menu {
    // 这里写我们的自定义代码
}

这就好比我们新建了一个门派,名字叫“My_Custom_Menu_Walker”,以后所有的菜单定制都从这里出发。

2. 重写 start_lvl() 方法

start_lvl()方法负责输出子菜单的开始标签,比如<ul>或者<ol>。我们可以重写这个方法来改变子菜单的HTML结构。

class My_Custom_Menu_Walker extends Walker_Nav_Menu {
    function start_lvl( &$output, $depth = 0, $args = array() ) {
        $indent = str_repeat("t", $depth);
        $output .= "n$indent<ul class="sub-menu custom-sub-menu">n"; // 添加自定义的class
    }
}

这段代码的意思是,在子菜单的<ul>标签上添加了一个custom-sub-menu的class。这样我们就可以通过CSS来控制子菜单的样式了。

3. 重写 end_lvl() 方法

end_lvl()方法负责输出子菜单的结束标签,比如</ul>或者</ol>

class My_Custom_Menu_Walker extends Walker_Nav_Menu {
    function end_lvl( &$output, $depth = 0, $args = array() ) {
        $indent = str_repeat("t", $depth);
        $output .= "$indent</ul>n";
    }
}

这段代码只是简单地输出了</ul>标签,没有做任何修改。

4. 重写 start_el() 方法

start_el()方法是整个Walker类中最核心的方法,它负责输出每个菜单项的开始标签,包括<li>标签和<a>标签。我们可以重写这个方法来修改菜单项的HTML结构,添加自定义的class,插入图片等等。

class My_Custom_Menu_Walker extends Walker_Nav_Menu {
    function start_el(  &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
        $indent = ( $depth ) ? str_repeat( "t", $depth ) : '';

        $classes = empty( $item->classes ) ? array() : (array) $item->classes;
        $classes[] = 'menu-item-' . $item->ID;

        $class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args, $depth ) );
        $class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';

        $id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args, $depth );
        $id = $id ? ' id="' . esc_attr( $id ) . '"' : '';

        $output .= $indent . '<li' . $id . $class_names .'>';

        $atts = array();
        $atts['title']  = ! empty( $item->attr_title ) ? $item->attr_title : '';
        $atts['target'] = ! empty( $item->target )  ? $item->target  : '';
        $atts['rel']    = ! empty( $item->xfn )    ? $item->xfn   : '';
        $atts['href']   = ! empty( $item->url )    ? $item->url   : '';

        $atts = apply_filters( 'nav_menu_link_attributes', $atts, $item, $args, $depth );

        $attributes = '';
        foreach ( $atts as $attr => $value ) {
            if ( ! empty( $value ) ) {
                $value = ( 'href' === $attr ) ? esc_url( $value ) : esc_attr( $value );
                $attributes .= ' ' . $attr . '="' . $value . '"';
            }
        }

        $title = apply_filters( 'the_title', $item->title, $item->ID );

        $item_output = $args->before;
        $item_output .= '<a'. $attributes .'>';
        $item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
        $item_output .= '</a>';
        $item_output .= $args->after;

        $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
    }
}

这段代码有点长,我们来分解一下:

  • $classes: 获取菜单项的class列表,并添加一个menu-item-{ID}的class。
  • $class_names: 把class列表转换成字符串,并添加到<li>标签上。
  • $id: 获取菜单项的ID,并添加到<li>标签上。
  • $atts: 获取菜单项的属性,比如titletargetrelhref,并添加到<a>标签上。
  • $title: 获取菜单项的标题。
  • $item_output: 拼接HTML代码,包括<a>标签,标题,以及$args中的beforeafter参数。
  • apply_filters( 'walker_nav_menu_start_el', ... ): 使用walker_nav_menu_start_el过滤器,允许其他插件或者主题修改菜单项的HTML代码。

重点来了! 我们可以在这个方法中做任何我们想做的事情。比如,我们可以给<a>标签添加自定义的class:

class My_Custom_Menu_Walker extends Walker_Nav_Menu {
    function start_el(  &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
        // ... 省略之前的代码 ...

        $atts = array();
        $atts['title']  = ! empty( $item->attr_title ) ? $item->attr_title : '';
        $atts['target'] = ! empty( $item->target )  ? $item->target  : '';
        $atts['rel']    = ! empty( $item->xfn )    ? $item->xfn   : '';
        $atts['href']   = ! empty( $item->url )    ? $item->url   : '';
        $atts['class']  = 'custom-link-class'; // 添加自定义的class

        $atts = apply_filters( 'nav_menu_link_attributes', $atts, $item, $args, $depth );

        // ... 省略之后的代码 ...
    }
}

或者,我们可以在菜单项中插入图片:

class My_Custom_Menu_Walker extends Walker_Nav_Menu {
    function start_el(  &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
        // ... 省略之前的代码 ...

        $title = apply_filters( 'the_title', $item->title, $item->ID );

        $item_output = $args->before;
        $item_output .= '<a'. $attributes .'>';
        $item_output .= '<img src="' . get_template_directory_uri() . '/images/menu-icon.png" alt="' . esc_attr( $title ) . '">'; // 插入图片
        $item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
        $item_output .= '</a>';
        $item_output .= $args->after;

        $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
    }
}

5. 重写 end_el() 方法

end_el()方法负责输出每个菜单项的结束标签,比如</li>标签。

class My_Custom_Menu_Walker extends Walker_Nav_Menu {
    function end_el( &$output, $item, $depth = 0, $args = array() ) {
        $output .= "</li>n";
    }
}

这段代码只是简单地输出了</li>标签,没有做任何修改。

四、 如何使用自定义的Walker类?

定义好了自定义的Walker类,接下来就要告诉WordPress使用它来生成菜单。这很简单,只需要在wp_nav_menu()函数中传入'walker' => new My_Custom_Menu_Walker()参数即可。

wp_nav_menu( array(
    'theme_location' => 'primary', // 菜单位置
    'menu_class'     => 'nav-menu', // 菜单class
    'walker'         => new My_Custom_Menu_Walker(), // 使用自定义的Walker类
) );

这段代码的意思是,使用My_Custom_Menu_Walker类来生成primary位置的菜单。

五、 实战案例:带图标的菜单

我们来做一个稍微复杂一点的例子:给菜单项添加自定义的图标,图标的URL保存在菜单项的描述字段中。

首先,我们需要在后台菜单设置中,打开“描述”字段的显示。在“显示选项”中勾选“描述”即可。

然后,在菜单项的描述字段中填写图标的URL。

最后,修改start_el()方法:

class My_Custom_Menu_Walker extends Walker_Nav_Menu {
    function start_el(  &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
        // ... 省略之前的代码 ...

        $title = apply_filters( 'the_title', $item->title, $item->ID );

        $item_output = $args->before;
        $item_output .= '<a'. $attributes .'>';

        if ( ! empty( $item->description ) ) {
            $item_output .= '<img src="' . esc_url( $item->description ) . '" alt="' . esc_attr( $title ) . '">'; // 插入图标
        }

        $item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
        $item_output .= '</a>';
        $item_output .= $args->after;

        $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
    }
}

这段代码的意思是,如果菜单项的描述字段不为空,就插入一个<img>标签,src属性为描述字段的值,alt属性为菜单项的标题。

六、 wp_nav_menu函数的参数

wp_nav_menu()函数有很多参数,可以用来控制菜单的输出。下面是一些常用的参数:

参数名 描述
theme_location 菜单位置,在register_nav_menus()函数中注册的菜单位置。
menu 菜单ID,菜单名称,或者菜单对象。如果theme_location参数有效,则忽略此参数。
container 包裹菜单的容器标签,比如divnav等。默认为div
container_class 容器标签的class。默认为menu-{菜单名}-container
container_id 容器标签的ID。
menu_class 菜单的class。默认为menu
menu_id 菜单的ID。
echo 是否输出菜单。默认为true。如果设置为false,则返回菜单的HTML代码。
fallback_cb 如果菜单不存在,则调用的函数。默认为wp_page_menu()
before 菜单项链接前面的文本。
after 菜单项链接后面的文本。
link_before 菜单项链接文本前面的文本。
link_after 菜单项链接文本后面的文本。
depth 菜单的深度。默认为0,表示不限制深度。
walker 用于生成菜单HTML代码的Walker对象。

七、 使用Filter Hooks进行定制

除了继承和重写Walker_Nav_Menu类,我们还可以使用WordPress提供的Filter Hooks来定制菜单。比如:

  • wp_nav_menu_objects: 在菜单项对象被传递给Walker类之前,修改菜单项对象。
  • nav_menu_css_class: 修改菜单项的CSS class。
  • nav_menu_item_id: 修改菜单项的ID。
  • nav_menu_link_attributes: 修改菜单项链接的属性。
  • walker_nav_menu_start_el: 修改菜单项的HTML代码。

使用Filter Hooks可以避免直接修改Walker_Nav_Menu类的代码,更加灵活和安全。

八、 总结

今天我们深入探讨了WordPress的菜单定制,学习了如何通过继承和重写Walker_Nav_Menu类,以及使用Filter Hooks来修改菜单的HTML结构。希望大家能够灵活运用这些技巧,打造出独一无二的导航菜单,提升网站的用户体验。

记住,菜单定制没有固定的套路,只有不断尝试和创新,才能找到最适合自己的解决方案。

好了,今天的讲座就到这里,谢谢大家!如果有什么问题,欢迎留言讨论。 下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注