Deep anchor links, also known as jump links, allow users to share direct links to specific parts of the article. They not only create a better user experience but are also good for social media sharing and direct linking to different document sections.

In this article, we will learn how to convert blog post headings into deep anchor links using vanilla JavaScript. We will not use any 3rd party plugin for this purpose.

Note: If your blog is running on WordPress, you should rather use a plugin for adding deep anchor links. This solution is more suitable for static and non-WordPress blogs.

For this purpose, you do not need to add IDs to headings or worry about URLs. In fact, we will be using the heading text to generate an ID and an anchor link next to the heading text. Already have the IDs? No worries. We won't change them.

HTML Markup

Here is an example HTML markup we want to add deep anchor links to.

<!DOCTYPE html>
<html lang="en">
<body>

<h1>15 ways food processors are completely overrated</h1>
<time>24 March, 2019</time>

<div class="post-content">
    <h2>Introduction</h2>
    <p>...</p>

    <h2>A Brief History</h2>
    <p>...</p>

    <h3>Fast Food</h3>
    <p>...</p>

    <h3>Home Made Food</h3>
    <p>...</p>

    <h2>Conclusion</h2>
    <p>...</p>
</div>
</body>
</html>

As you can see above, we have multiple h2 and h3 headings without IDs. Our goal is to convert these headings into deep anchor links.

Let's start writing JavaScript to achieve our links generation goal. The first step is to generate IDs and links based on heading texts. The following JavaScript code snippet does this job:

document.querySelectorAll('.post-content h1, .post-content h2, .post-content h3, .post-content h4').forEach($heading => {
  //create id from heading text
  var id =
    $heading.getAttribute('id') ||
    $heading.innerText
      .toLowerCase()
      .replace(/[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, '')
      .replace(/ +/g, '-')

  //add the id to the heading
  $heading.setAttribute('id', id)

  //append parent class to heading
  $heading.classList.add('anchor-heading')

  //create an anchor
  $anchor = document.createElement('a')
  $anchor.className = 'anchor-link'
  $anchor.href = '#' + id
  $anchor.innerText = '#'

  //append anchor after heading text
  $heading.appendChild($anchor)
})

The above JavaScript code selects all h1, h2, h3 and h4 inside the .post-content selector. Why this class selector? Because we want to add anchor links to headings only inside the article content and not the whole page.

forEach() is a JavaScript element method that calls the provided function once for each heading. Inside the provided function, first of all, we create an ID based on the existing ID value or the heading text. The generated ID is then added to the heading using the setAttribute() method.

In the end, we create a new anchor element, set its URL, and display text (#) before appending it next to the heading text. In short, if we have the heading like this <h2>Introduction</h2>, it will be converted to the following:

<h2 id="introduction" class="anchor-heading">Introduction<a class="anchor-link" href="#introduction">#</a></h2>

You may also want to add smooth scrolling to the newly generated anchor links. By default, if you click on any such link, it will jump suddenly to the top. You can change this behavior with the following code so that it’s a smooth scrolling transition.

document.querySelectorAll('a.anchor-link').forEach($anchor => {
  $anchor.addEventListener('click', function (e) {
    e.preventDefault()
		
    document.querySelector(this.getAttribute('href')).scrollIntoView({
      behavior: 'smooth',
      block: 'start' //scroll to top of the target element
    })
  })
})

At this point, we are done with link generation and their smooth scrolling to the top. But it only works after the page is fully loaded. If you want to skip directly to a certain section of the article by entering an anchor link in the browser window, we need to do a little more work:

if (window.location.hash.length > 0) {
  setTimeout(function () {
    document.querySelector('a[href="' + window.location.hash + '"]').click()
  }, 150)
}

Notice the setTimeout() function. We are using this to delay our manual navigation for 150ms so that the deep anchor links are generated and added to DOM.

Note: Make sure you run the above JavaScript codes only after the page is loaded. Otherwise, it won't add anchor links. Here is how you can do this: window.onload = function() { /*add your code here*/ }

Finally, let's add some CSS styling to show deep anchor links only when the user hovers over the headings. This is exactly what I am doing on my blog. If you hover over any heading, you will see an anchor link.

.anchor-heading .anchor-link {
    display: inline-block;
    padding-left: .25rem;
    text-decoration: none;
    opacity: 0;
    transition: opacity ease-in-out .25s;
}

.anchor-heading .anchor-link:hover {
    opacity: 1 !important;
}

.anchor-heading:hover .anchor-link {
    opacity: .5;
}

By default, anchor links are invisible (opacity is 0). When you hover over the heading, the anchor link opacity is increased to .5 or 50%. The opacity is increased to 100% when you hover directly at the link.

Bonus: jQuery Solution

If you are already using jQuery on your website, adding deep anchor links is even easier. Replace the above vanilla JavaScript code with the following jQuery equivalent code:

$(document).ready(function () {
  $('.post-content h1, .post-content h2, .post-content h3, .post-content h4').each(function () {
    //create id from heading text
    var id =
      $(this).attr('id') ||
      $(this)
        .text()
        .toLowerCase()
        .replace(/[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, '')
        .replace(/ +/g, '-')

    //add the id to the heading
    $(this).attr('id', id)

    //append parent class to heading
    $(this).addClass('anchor-heading')

    //create an anchor
    var anchor = $('<a class="anchor-link" href="#' + id + '">#</a>')

    //append anchor link after heading text
    $(this).append(anchor)
  })

  //add smooth scroll for anchor links
  $(document).on('click', 'a.anchor-link', function (e) {
    e.preventDefault()
    $('html, body')
      .stop()
      .animate(
        {
          scrollTop: $($(this).attr('href')).offset().top - 50
        },
        1000,
        'linear'
      )
  })

  //navigate to anchor if available
  if (window.location.hash.length > 0) {
    $('a[href="' + window.location.hash + '"]').trigger('click')
  }
})

Source code: Download the complete source code from GitHub available under MIT license.

Conclusion

That's all for converting headings in a blog post or any other HTML document into deep anchor links. We discussed both vanilla JavaScript and jQuery-based solutions. It is not the only way to add deep anchor links. There exist a lot of other creative ways to add such links.

If you need a more advanced solution (more options to show/hide icons, link placement, etc.), I recommend anchor.js. It is a ~6KB minified JavaScript file that lets you add deep anchor links on the fly. But if you care about the website performance, just add the above few lines of code and you are good to go.

✌️ Like this article? Follow me on Twitter and LinkedIn. You can also subscribe to RSS Feed.