John Russell Blog

WordPress Menu Anchor

I recently had the need to add a series of anchor links to a WordPress menu that linked to specific sections of a page. I found this to be tedious, since the page URL needed to be hard coded into the custom link menu item and if the page URL was ever updated each anchor needed to be updated as well. To complicate things further, when looking at the custom link within the menu editor it’s not immediately obvious that the link is an anchor if the URL is longer than the input box.

What I wanted to be able to do is simply create a custom link menu item and set the link value to #my-custom-anchor. Generally if you create a custom link menu item and set the link to be something like #my-custom-anchor it will work, if you’re already on the page for the given anchor, however it will also navigate to “my-custom-anchor” even if you’re on another page where “my-custom-anchor” doesn’t exist.

What I needed was for the anchor to navigate to the desired page and then to the specific section of the page. To do this, the URL would need to have the page URL prepended to the anchor hash. This would allow anchor links to be clicked from anywhere on the website and still navigate to the correct page, and correct section, but if the page was already being viewed the page would not reload (so scrolling animations would still work as well).

Since all of the anchor links were nested under a page menu item, as illustrated in the example below, I was able to filter the menu and prepend the URL with the URL of its’ parent menu item.

Menu Structure Example

  • Page Menu Item 1
    • Page Section 1 Anchor Menu Item
    • Page Section 2 Anchor Menu Item
      • Page Section 2 Sub Section 1 Anchor Menu Item
      • Page Section 2 Sub Section 2 Anchor Menu Item
    • Page Section 3 Anchor Menu Item
  • Page Menu Item 2
  • Page Menu Item 3

In the above example each “Page Section… Menu Item” would link to “Page Menu Item 1” and scroll to the specified section of that page. This is very useful for long pages with many sections and sub-sections.

The Solution is in the Code

To accomplish this I created a menu filter function, that gets added to the themes functions.php file, that searches for menu items with URLs that begin with a # and then prepends the URL with the URL of its’ parent menu item.

function lb_menu_anchors($items, $args) {
	$urls = array();
	$custom_anchor_links = array();
	
	foreach ($items as $key => $item) {
		$urls[$item->ID] = $item->url;
		
		if ($item->object == 'custom' && substr($item->url, 0, 1) == '#') {
			$custom_anchor_links[] = $item;
		} 
	}
	
	foreach ($custom_anchor_links as $custom_anchor_link) {
		if (strpos($urls[$custom_anchor_link->menu_item_parent], '#') === false) {
			$custom_anchor_link->url = $urls[$custom_anchor_link->menu_item_parent] . $custom_anchor_link->url;
		} else {
			// multiple nested menu items with anchors
			$custom_anchor_link->url = explode('#', $urls[$custom_anchor_link->menu_item_parent])[0] . $custom_anchor_link->url;
		}
	}
	
	return $items;
}
add_filter('wp_nav_menu_objects', 'lb_menu_anchors', 10, 2);

This allowed me to create custom link menu items with easily readable anchor links, that could be nested, and would also dynamically update if the page URL ever changed. Another benefit to this method vs “hard coding” an anchor link menu item is that the URL continues to work in a Domain Mapped environment, and across dev, staging, and production environments as well.

Comments

11 responses to “WordPress Menu Anchor”

  1. Isaac Avatar

    Delicious, works perfectly. However it blocks a smooth scroll i have enabled via jQuery. Any reason how to get this back? Or what might be stopping it from doing what need be.

    1. laubsterboy Avatar
      laubsterboy

      The lb_menu_anchors function is filtering the menu links on the back-end before jQuery is ever even loaded, so I’m not sure why this would affect the smooth scroll. I’d have to know what plugin or jQuery plugin is being used so that I can test it myself, or see your site.

      1. Isaac Avatar

        Yeah thats exactly what I thought.

        http://www.socialmediacollege.com

        Currently got a test page loaded in there so you will need to login.

        Its also causes an error with this code here ;

        $(‘a’).click(function(){
        $(‘html, body’).animate({
        scrollTop: $( $(this).attr(‘href’) ).offset().top -120
        }, 500);
        return false;
        });

        Which just allows an offset due to the header.

        Doesn’t make sense why its doing this.

        Cheers

        1. laubsterboy Avatar
          laubsterboy

          Thanks for providing additional details. I was able to figure out what was causing the problem. The filter that I posted modifies all links that begin with a ‘#’ and appends the proper page url in front of that ‘#anchor’. So instead of the href value being ‘#why’ on the Why SMC page it’s instead ‘http://socialmediacollege.com/about-us/#why’. So when your jQuery script searches for the corresponding id to the link it’s essentially doing this:

          scrollTop: $( ‘http://socialmediacollege.com/about-us/#why’ ).offset.top – 120

          This is what causes the error. That string isn’t a valid selector. What jQuery is expecting in this case is the hash part of the href.

          I tested this script and it worked just fine:

          $('a').click(function() {
          	var href = $(this).attr('href');
          	if (href.indexOf('#') !== -1) {
          		var id = href.split('#')[1];
          		$('html, body').animate({
          			scrollTop: $('#' + id ).offset().top -120
          		}, 500);
          	}
              return false;
          });
          

          I hope that fixes the problem with the scroll animation no longer working. I also edited your comment to remove the login information.

          1. Isaac Avatar

            Beautiful Thanks mate, will test know and get back to you.

            Cheers

          2. Isaac Avatar

            Works perfect, except the rest of the menu doesn’t know. I will have a look and try and figure it out. Thanks for your help.

  2. Faye Avatar
    Faye

    Thanks a bunch, I’ve been searching all over how to prepend my #anchor links for a one page site. Modified this slightly to prepend the site url ahead of the # instead of the parent link.

    function lb_menu_anchors($items, $args) {
    	foreach ($items as $key => $item) {
    		if ($item->object == 'custom' && substr($item->url, 0, 1) == '#') {
    			$item->url = site_url() . $item->url;
    		} 
    	}
    
    	return $items;
    }
    add_filter('wp_nav_menu_objects', 'lb_menu_anchors', 10, 2);
    1. dio Avatar
      dio

      You have no clue how much I love you right now…I have destroyed my brain for days. Thanks a lot!

    2. shawn Avatar
      shawn

      I spent 6 hours trying to figure this out. And boom.
      Thank you for sharing.
      LONG LIVE LAUBSTER!!!
      LONG LIVE FAYE!!!

  3. tittu Avatar
    tittu

    Oh… My… God…
    I’ve been searching this for a while.
    I’m no developer and PHP is reall hard to me, but mate you have no idea how much you helped me with this snipet!
    Thanks a lot!

  4. Domitille Avatar
    Domitille

    Yeaaaah, thank you so much !!

Leave a Reply

Your email address will not be published. Required fields are marked *