Learn Browser APIs in about a week!

Learning should be fun and quick; no need to spend ages in just one single course.

Start learning  

#33 Programming
19 mins   /

What is the hashchange event in JavaScript?

Learn what exactly is the hashchange event in JavaScript and how to use it for practical purposes in a web application.

These days, single-page applications (SPAs) are pretty common on the web. They rely on heavy amounts of meticulous AJAX programming, leveraging modern browser APIs, most importantly the popstate event. Sometimes, you'll also find SPAs that run on a fragment-based URL navigation system.

Such SPAs are built around the notion of a fragment in the URL and are powered by the hashchange event in JavaScript. But even outside of SPAs, hashchange is used to power simple user-interface utilities, for example, remembering that the user has opened up the menu and, likewise, enabling the back button of the browser to be used to close the menu and resume to the page.

In this article, I'll cover the hashchange event in detail, covering such things as what is meant by the term "hash", when does hashchange fire, and even present an example of a simple utility feature on a website using hashchange. Let's begin!

What is meant by "hash"?

First and foremost, let's settle on the meaning of the word "hash" in hashchange because once you get that, the rest is easy to understand. Fortunately, it's nothing new; it refers to the fragment or hash part of a URL as illustrated below:

The anatomy of an HTTP URL, with the fragment highlighted.
The anatomy of an HTTP URL, with the fragment highlighted.

The fragment serves one important purpose: it identifies the section within the current resource — mostly, an HTML document — that is pointed to by the URL. If the resource is an HTML document, which as I said is mostly the case, the fragment's name corresponds to the id of an HTML element in the document.

For example, let's say you have a <section> with the attribute id="intro", as follows:

HTML
<section id="intro">
   ...
</section>

This <section> can be pointed to by a URL like:

https://www.example.com/about#intro

The #intro part in the URL identifies a fragment in the underlying document by the name "intro" — which is simply a <section> element in this case.

For the very reason that a fragment in a URL identifies a section, instead of multiple sections, it's recommended by the HTML standard that you have unique ids in an HTML document.

With this basic idea of "hash" in mind, let's now get to unraveling the intuition behind the hashchange event.

Understanding hashchange

The hashchange event fires every time the hash part of the current document's URL changes in the browser's address bar. The event's target is the window object.

The hashchange event does NOT fire on any other target except for window. Remember this.

There are two particularly interesting things regarding changing the hash of a URL:

  1. Hash changes are reflected in the browser's history. So, for instance, if you go from #a1 to #a2, the browser's history is updated — a new entry is added for #a2.
  2. Hash changes don't cause a page reload. You can try this yourself: go the address bar for any webpage and add a fragment like #demo at the end of the URL — the URL would change but the webpage won't reload.

Using hashchange

Okay, enough of me talking, let's consider some examples to better understand hashchange.

Logging a message

We shall create a simple program that logs a message each time the URL's hash changes. Quite straightforward to do this: set up a hashchange event listener on window and make the log in the callback:

JavaScript
window.addEventListener('hashchange', () => {
   console.log('Hash changed!');
});

Now in order to test this code, you need to cause a change in the URL's hash, which then triggers the hashchange event. An easy way is to set up some dummy links in the document that point to different fragments. (It's not required for the fragments to actually exist.)

Let's create 3 dummy links:

HTML
<a href="#sect1">Go to #sect1</a>
<a href="#sect2">Go to #sect2</a>
<a href="#sect3">Go to #sect3</a>

Perfect. You can try the live example as follows: Live Example

As soon as we click on one of these links, the URL's hash in the address bar changes and that causes the dispatch of the hashchange event on window, which ultimately fires the callback making the log.

Since, in this case there are no elements on the page with id equal to "sect1", "sect2" or "sect3", visiting the links above won't cause a document scroll. However, if you really have such elements in the document, then clicking on a link would change the URL's hash and scroll the document to the exact location where the corresponding element lies.

Logging the previous and new URLs

Let's add some spice to this example. Instead of showing a static message each time the hash changes, let's output the new and the previous URL's hash. The location.hash property can be handy in this respect.

The location.hash property

Quickly describing it, location.hash returns the hash part of the current document's URL. If there is no hash in the URL, location.hash returns an empty string or else it includes the '#' character followed by the hash, for e.g. '#intro'.

To start with, we create two variables: prevHash and currentHash to store the previous and current URL's hashes, respectively. Initially, since there is no previous URL, prevHash is null.

JavaScript
let prevHash = null;
let currentHash = location.hash;

Here's the modified hashchange listener's callback that logs the previous and current URL's hashes each time the hashchange event fires:

JavaScript
let prevHash = null;
let currentHash = location.hash;

window.addEventListener('hashchange', () => {
   prevHash = currentHash;
   currentHash = location.hash;

   console.log('Previous hash:', prevHash);
   console.log('Current hash:', currentHash);
});

Try this example as follows: Live Example

Practical application of hashchange

Since the browser's history is updated by virtue of a hash change, this can be leveraged by applications as a way to preserve state without modifying the path of a URL (which is generally reserved to identify, in the broader sense, a resource). And that, in turn, can be used to show widgets and preserve the browser's back button behavior to "close" that widget.

For example, let's say you have a webpage with a button to open up a menu. When it's just a button, it probably fires a click event, you handle that event and modify the DOM to show up a menu of some kind.

But now imagine that instead of a button, you have a link pointing to a fragment, let's say #menu. Clicking on the link changes the hash in the URL and, of course, also fires the click event. As before, you handle th event and modify the DOM to show up the menu. But this time, notice what's different. The URL has effectively encoded the fact that your page has the menu shown.

This can be bookmarked and later when you revisit it, your application can read the URL to see that it must show the menu because that's the state in which you bookmarked the webpage.

And I'm not making this up. Some notable websites use similar approaches to power interactive widgets. Consider Wikipedia. Here's the page on Wikipedia on JavaScript:

Screenshot of Wikipedia's page on JavaScript
Screenshot of Wikipedia's page on JavaScript.

When I click on the image at the right side showing the source code of JavaScript, the image viewer shows up:

Wikipedia's image viewer widget, displayed after clicking on the image.
Wikipedia's image viewer widget, displayed after clicking on the image.

Most importantly, notice what happened to the URL in the address bar, as illustrated below — Wikipedia introduces a hash followed by a string of characters that's their way of representing the image viewer widget on the underlying page:

Wikipedia using the hash part in a URL to represent its image viewer widget.
Wikipedia using the hash part in a URL to represent its image viewer widget.

The benefit of this approach is that you can bookmark the image viewer and directly land upon it from the bookmark, and you can also use the back button to close the image viewer.

Without hashchange, this wouldn't have been possible to build.

The string following the # probably represents a path to the image on Wikipedia but that's an implementation detail best left to Wikipedia.

Anyways, coming back to our event, whenever the hash/anchor of the current page's URL changes, JavaScript fires the hashchange event.

Things to note

While working with the hashchange event, there are certain things to take note of.

Only fired on a change

The hashchange event fires only when there is a change in the URL's hash. This means that when a page is initially loaded with a given hash, the hashchange event does NOT fire. Why?

Simply because in this case, no change has occurred in the hash. The page was requested for with a given hash already in the URL; that hash doesn't constitute a hash change so to speak.

Alright, but why is knowing this important? you ask.

To make sure that if you have a feature in your application that takes place when the hash becomes equal to some value, it must take place even when the underlying document is initially loaded with that hash in the URL (i.e. there is no hash change).

To do so, you have to put the code for the feature, in addition to inside the hashchange listener's callback, outside of the listener as well, probably in the window's load listener's callback. This is in order to put the feature into action if the document's URL initially had the hash in it.

window is the only possible target

Secondly, as mentioned above, the hashchange event has only one possible target: window. The event does NOT fire on any other object.

This means that the following bunch of statements are useless to handle any hash changes in the URL:

JavaScript
// useless! document.addEventListener('hashchange', callback); // useless! document.documentElement.addEventListener('hashchange', callback); // useless! document.body.addEventListener('hashchange', callback);

The event never fires on the element nodes document, document.documentElement, document.body, or any other node. The one and only way is the use window. Period.

Bilal Adnan

Hi there! 👋 I'm the founder of Codeguage — basically the guy who's trying to make life easy for absolute beginners entering into computer science. You can follow me on LinkedIn or Medium to stay up-to-date with my conversations.