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

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

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 starting 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 id to heading
  $heading.setAttribute('id', id);

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

  //create 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 existing ID value or the heading text. The generated ID is then added to the heading using setAttribute() method.

At 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 links 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 id to heading
		$(this).attr('id', id);

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

		//create 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 exists a lot of other creative ways to add such links.

If you need a more advance solution (more options to show/hide icons, links placement etc.), I recommend anchor.js. It is a ~6KB minified JavaScript file that let 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.

If you have any question or suggestion, please feel free to send me a tweet anytime.

✌️ Like this article? Follow @attacomsian on Twitter. You can also follow me on LinkedIn and DEV. Buy me a coffee (cost $3)


Need help to start a new Spring Boot or MEAN stack project? I am available for contract work. Hire me to accomplish your business goals with engineering and design. Let’s talk about your project: hi@attacomsian.com.