Smooth Scrolling HTML Bookmarks using JavaScript (natively or jQuery)

Posted: April 17th, 2018

HTML bookmarks let you quickly go to any section on a page using an anchor link, such as in the following example:

<a href="section1">Jump to Section 1</a>
<div id="section1">This is section 1 content</div>

By default, the process is abrupt and instantaneous, like being yanked out of bed early in the morning. Not a pleasant experience for many people.

There are many JavaScripts that add a smooth transition to the behaviour of HTML bookmarks, although all rely on animating the document's scrollTop position to simulate scrolling the page. It's all rather roundabout and inefficient.

In modern browsers, there is now a native way to smoothly scroll the page to a specific element via an anchor link. In this tutorial, I'll show you how to use ES6 script and the DOM method scrollIntoView() to achieve this with minimal code and no JavaScript frameworks.

And for those of you that still need legacy browser support, I'll also throw in a jQuery replacement script that works in older browsers as well.

Demo: Click on the below anchor links. Smooth Scrolling works in Chrome and Firefox. Bookmarks themselves work in ALL browsers:

Smooth HTML Bookmark Scrolling using Native JavaScript

To enable smooth scrolling to any element on the page natively, we can turn to an old JavaScript method element.scrollIntoView(), which has been updated in modern browsers to support smooth travel to an element on the page.

In all newer versions of Chrome, Firefox, and Opera, the scrollIntoView() method supports some new options, one of which is the "behavior" option that enables smooth scrolling:

	behavior: 'smooth',
	block: 'start' // scroll to top of target element

Element is the DOM element you wish to navigate to. There are other options besides "behavior", which you can learn about on MDN.

All that we need to do is to target all HTML anchor links on the page, and override their default behaviour to use element.scrollIntoView() to gently smooth to the element they reference. Using ES6 JavaScript, the whole affair is painless.

How's the final result using ES6 JavaScript:


Full Code:

let anchorlinks = document.querySelectorAll('a[href^="#"]')

for (let item of anchorlinks) { // relitere 
	item.addEventListener('click', (e)=> {
		let hashval = item.getAttribute('href')
		let target = document.querySelector(hashval)
			behavior: 'smooth',
			block: 'start'
		history.pushState(null, null, hashval)

With just the above snippet, either added at the end of your page or inside a wrapper that's executed when the page has loaded, all anchor links on the page will smoothly scroll to the requested element when clicked on. Cool!

Notice the highlighted line above. It's a cool trick that updates the browser's URL silently with a hash (#target) after each jump, so users can easily navigate to that element via the browser's URL as well, much like with a real HTML bookmark. More on this below.

Using history.pushState() to update the browser's URL History

By default, calling element.scrollIntoView() doesn't augment the browser's URL with a navigable hash. This is a huge shortcoming, made worse by the lack of any events that fire when the method has finished scrolling to the target element. This means we can't write custom code that update the browser's URL manually after the transition.

You may be tempted to simply use JavaScript's location.hash property to modify the browser's URL. There is a problem with that, however. Updating location.hash causes the browser to immediately jump to the target element on the page, which interrupts any animation when called inside the scrollIntoView() method.

And this is where history.pushState() can come in real handy. It appends to the browser's current URL silently, without triggering any further action. history.pushState() accepts three parameters:

  • State Object: A literal object such as {bar: "foo"} that is associated with this new history entry. It can be anything that can be serialized in JavaScript, with the data accessible using the popstate event handler by way of the state property.

  • Title: The title of the history entry. Currently ignored by most browsers.

  • URL: An absolute or relative URL to replace or append the current browser URL with. In the former case, the URL must be from the same domain as the site itself, or an error is thrown.

We are merely interested in updating the browser's URL with a hash each time an element is scrolled to, so a line like this is all we need:

history.pushState(null, null, "#newhash")

That's it.

Browser Support for Native HTML Bookmark Scrolling

The above snippet brings together a few modern features in JavaScript, from ES6 script, history.pushState() to the "behavior" option of scrollIntoView().

The code works in all recent versions of Chrome and Firefox. IE Edge does not yet support the "behavior" option. Regardless however, the great news is, the script doesn't impede on legacy browsers from jumping to the target elements if they don't support the snippet. In other words, it's all good.

Using jQuery to Create Smooth Scrolling Bookmark Links

If your site already uses the jQuery framework, then it makes sense to just take advantage of it on your site whenever possible.

With Scrolling HTML Bookmarks, you cgan use the following jQuery script to accomplish this. It has the added benefit of working in IE Edge and below, plus the ability to specify the transition duration and any custom actions after the target element is scrolled to:

	var $root = $('html, body');
	$('a[href^="#"]').click(function() {
		var href = $.attr(this, 'href');
			scrollTop: $(href).offset().top
		}, 500, function () {
			window.location.hash = href;
		return false;


The downside of using jQuery is obviously the large footprint of jQuery itself. The scrolling script is also much less efficient compared to the native version. But the benefits if you already have jQuery loaded on your site is worth it in my opinion.

It's great to see browsers moving towards native smooth scrolling to HTML bookmarks. In my opinion it should be the default behaviour in browsers, but that's just a lone man's opinion, one who doesn't like abrupt changes with anything in life, including inside the browser.