JavaScript: Replacing anchor links with JavaScript
Anchors in HTML are essentially bookmarks within a page that can be targeted directly by adding an anchor reference starting with '#' to the URL. The browser will then jump to the specified anchor.
However when a link targets an anchor on the same page the browser 'Back' button will no longer take the browser back to the previous page, but to the previous anchor.
What is an anchor
There are two ways of marking anchor points on the page. The old method is to use the <a> tag with a name attribute. It's a bit awkward as you can see:
<h2><a name="some-heading"></a>Some Heading</h2>
<h2><a name="another-heading">Another Heading</a></h2>
The more recent technique is to assign an id where you want the anchor:
<h2 id="some-heading">Some Heading</h2>
In either case we can send the browser to that section of the page by appending the anchor to the URL using a hash (#):
http://www.example.com/some-page.html#some-heading
For linking to an anchor on the same page we use:
<a href="#some-heading">Click here to jump to Some Heading</a>
<a href="#another-heading">Click here to jump to Another Heading</a>
An id value must be unique on the page and start with a letter. It can then contain numbers, dashes and underscores. Colons and periods are also permissable, but can cause problems with certain JavaScript libraries.
Jumping to anchors with JavaScript
While less common now there is a simple technique for jumping to anchor points using a SELECT input for navigation. It requires just a touch of JavaScript:
<form method="GET" action="#" onsubmit="return false;">
<select onchange="self.location.href = '#' + options[selectedIndex].value;">
<option>-- jump navigation --</option>
<option value="some-heading">Some Heading</option>
<option value="another-heading">Another Heading</option>
</select>
</form>
The result, which can be nicely styled using CSS, is the following:
You will see that when an option is selected the browser jumps to a new anchor point on the page and the anchor (#) appears in the URL.
A pure JavaScript solution
In all the above examples the browser is taken to different parts of the page my modifiying the URL, and this makes the 'Back' button behaviour less intuitive.
What if we could keep the benefits of using anchor points, but remove the drawback of polluting the browser history?
The solution is to use the scrollIntoView method which is supported in all major browsers. It scrolls to the (any) selected element without changing the URL.
document.getElementById("some-heading").scrollIntoView(true);
We still need to mark the anchor points using an id, but now the browser history remains unaffected.
Applying this to the SELECT navigation we end up with:
<form method="GET" action="#" onsubmit="return false;">
<select onchange="document.getElementById(options[selectedIndex].value).scrollIntoView(true);">
<option>-- jump navigation --</option>
<option value="some-heading">Some Heading</option>
<option value="another-heading">Another Heading</option>
</select>
</form>
The behaviour is the same, only that the browser URL stays the same.
Applying a polyfill
The following code will parse your HTML page and override the function of any links that target anchor points on the same page. The link function will be replaced with a call to the scrollIntoView method of the target element:
<script>
window.addEventListener("DOMContentLoaded", function(e) {
// Original JavaScript code by Chirp Internet: www.chirpinternet.eu
// Please acknowledge use of this code by including this header.
var links = document.getElementsByTagName("A");
for(let i=0; i < links.length; i++) {
if(!links[i].hash) {
continue;
}
if(links[i].origin + links[i].pathname != self.location.href) {
continue;
}
(function(anchorPoint) {
links[i].addEventListener("click", function(e) {
anchorPoint.scrollIntoView(true);
e.preventDefault();
}, false);
})(document.getElementById(links[i].hash.replace(/#/, "")));
}
}, false);
</script>
On page load the function loops through all links (<A>) on the page looking for those that have a hash in the target address AND that point to the current page (self.location.href).
We add an onclick event that overrides the default browser behaviour. The script assumes that all anchor points have been marked up using the id technique.
You can see the effect on these two links. They are marked up as normal links, but when the page loads our script 'upgrades' them:
Unlike before, you can trigger these links as often as you want, but the Back button will still go to the previous page and not the previous anchor point you've visited.
Extra code will be required to support IE8, as usual, and you might want to check that the target id actually exists before assigning the new handler to a link.
Using querySelectorAll
For modern browsers we can clean up the code a bit by switching to querySelectorAll for identifying links on the page:
<script>
document.querySelectorAll("a[href*='#']").forEach(function(current) {
// Original JavaScript code by Chirp Internet: www.chirpinternet.eu
// Please acknowledge use of this code by including this header.
if(current.origin + current.pathname != self.location.href) {
return;
}
(function(anchorPoint) {
if(anchorPoint) {
current.addEventListener("click", function(e) {
anchorPoint.scrollIntoView({behavior: "smooth"});
e.preventDefault();
}, false);
}
})(document.querySelector(current.hash));
});
</script>
This gives us the flexibility to restrict the function to a certain section of the page:
document.querySelectorAll("#contentdiv a").forEach(...);
or to links with a specific class:
document.querySelectorAll("a.scrollto").forEach(...);
At the same time, we've set the behaviour of the scroll transition to 'smooth' which results in a smooth transition in supported browsers (all except for Internet Explorer and Safari). For more options, check out the links below.
Lawrence Ip 24 September, 2020
You're a lifesaver! I was able to completely ditch the whole 'navbar thing', by implementing your solution. The real bonus is that moving to and from page sections is super fast and intuitive and I now have a website that works as I have intended it - just like an app. A big THANK YOU!! You've just made my day (*•̀ᴗ•́*)و ̑̑
Rob Mitchell 29 August, 2019
For SPAs, I've been using the tried-n-true anchor JS onclick() listener pattern to hide/show "<section>" in my document. While you're suggestion is very cool, not sure I'm ready to switch over just yet.
Mouna El Majdaki 17 June, 2019
Thank you so much for your help, I needed to know about url redirect with anchor tag and this javascript function did the trick:
$(function(){
document.getElementById("content").scrollIntoView(true);
});
And I descovered that I have used it before but for different reason.
BR.
Anderson Makiyama 14 March, 2019
Very Cool! I'm using that script on some projects of mine, and it works perfectly!
Dave 20 April, 2016
Hi. will this work within html email as an anchor alternative. Especially with the iOS8 and above..?