I'm developing a webpage where depending on the next or back actions I do the correspondent animation, the problem comes when using the pushstate. When I receive the event how do I know if the user clicked back or forward history buttons using the Pushstate API?, or do I have to implement something myself?
How do I retrieve if the popstate event comes from back or forward actions with the HTML5 pushstate?
-
The popstate event says "please change to this state". It looks as if it assumes that you know what state you're currently in and therefore what you need to do to change state. – Neil Jan 24 '12 at 00:03
-
The problem is that the history is a stack, so if I have a list and I go forward, forward, forward, back, forward, back, forward, and the list is: [1,2,3,4,5], the history will be: [1,2,3,4,3,4,3,4]. In numbers is easy, but with urls is not that easy to know which url is the next, and which the previous. – Davsket Jan 24 '12 at 00:16
-
bennedich's comment below helped me a lot. It might be good to accept it so it more effectively help others as well. – Gavin Anderegg Aug 16 '13 at 13:53
2 Answers
You must implement it yourself which is quite easy.
- When invoking
pushState
give the data object a unique incrementing id (uid). - When
onpopstate
handler is invoked; check the state uid against a persistent variable containing the last state uid. - Update the persistent variable with the current state uid.
- Do different actions depending on if state uid was greater or less than last state uid.

- 12,150
- 6
- 33
- 41
-
18This works well when the page/view hierarchy is clearly defined, and it's always true that a certain page comes after another. When the navigation can be more dynamic, you can't rely on an incrementing UID. I've solved this by using sessionStorage to maintain a stack of the last X pages. Then, on a popstate event, you can check the stack from sessionStorage to see if the new page URL is the same as the URL of the N-2 page. Use sessionStorage instead of a normal variable, so this is persisted between page boundaries. – frontendbeauty Oct 31 '13 at 19:55
-
25@frontendbeauty I think you should post your proposal as the answer with code snippet. – andilabs Sep 30 '14 at 08:47
-
2in addition, instead of checking if the value is greater/lesser, you can write a state change manager that checks the id to see what the current state is and what state it's moving to, then do the corresponding animation – Changbai Li Apr 13 '15 at 08:09
-
If it's so easy, why didn't you write the implementation? I simply can't see how to make it bugless for any possible use case. No matter where I will keep the current state, I can figure out how to abuse it. – Atomosk Feb 17 '18 at 06:00
-
2@frontendbeauty what if you are on page you are in the middle page b and your stack looks like this `[a, b, a]` you get `popstate` event, how do you know if it was back or forward? – david_adler May 28 '21 at 15:40
This answer should work with a single page push-state app,
or a multi-page app, or a combination of the two.
(Corrected to fix the History.length
bug addressed in Mesqualito’s comment.)
How it works
We can easily listen for new entries to the history stack. We know that for each new entry, the specification requires the browser to:
- “Remove all the entries in the browsing context’s session history after the current entry”
- “Append a new entry at the end”
At the moment of entry, therefore:
new entry position = position last shown + 1
The solution then is:
- Stamp each history entry with its own position in the stack
- Keep track in the session store of the position last shown
- Discover the direction of travel by comparing the two
Example code
function reorient() // After travelling in the history stack
{
const positionLastShown = Number( // If none, then zero
sessionStorage.getItem( 'positionLastShown' ));
let position = history.state; // Absolute position in stack
if( position === null ) // Meaning a new entry on the stack
{
position = positionLastShown + 1; // Top of stack
// (1) Stamp the entry with its own position in the stack
history.replaceState( position, /*no title*/'' );
}
// (2) Keep track of the last position shown
sessionStorage.setItem( 'positionLastShown', String(position) );
// (3) Discover the direction of travel by comparing the two
const direction = Math.sign( position - positionLastShown );
console.log( 'Travel direction is ' + direction );
// One of backward (-1), reload (0) and forward (1)
}
addEventListener( 'pageshow', reorient );
addEventListener( 'popstate', reorient ); // Travel in same page
See also a live copy of the code.
Limitation
This solution ignores the history entries of external pages, foreign to the application, as though the user had never visited them. It calculates travel direction only in relation to the last shown application page, regardless of any external page visited in between. If you expect the user to push foreign entries onto the stack (see Atomosk’s comment), then you might need a workaround.

- 3,731
- 23
- 31
-
Do some navigation on your site, open an external link, press browser back button twice, long press browser forward button and pick external site session storage, press back. Maybe I'm way too catious :). Also I think your function can be replaced with `const direction = Math.sign(history.length - (sessionStorage.getItem('stateLastShown') || 0)); sessionStorage.setItem('stateLastShown', history.length);` since you always put `history.length` in history state. – Atomosk Mar 19 '18 at 16:25
-
@Atomosk (1) You’re right, external links aren’t handled properly. I added a warning about that. (2) I think your pruned code will fail. [History.length](https://developer.mozilla.org/en-US/docs/Web/API/History/length) determines the position only for a *new entry* to the stack. You also need code to handle the other case, which is a *revisited* entry. – Michael Allan Mar 20 '18 at 20:28
-
6Yeah you right. I'll never get used to how poor frontend apis are. Cheking how far I can go with back/forward button should be a single line, but somebody decided otherwise. – Atomosk Mar 21 '18 at 03:21
-
2Browser back/forward history has a fixed limit, in Chrome its 50. When you reach that limit `history.length` will always yield the same number (50 in my case) thus making this solution to always return 0 as direction, because all state entries would have identical position. – Mesqalito Jan 28 '19 at 15:37
-
@Mesqalito You're right, the browser limits on `History.length` make it unreliable. I changed the answer to use session variable `positionLastShown` instead. – Michael Allan May 09 '19 at 09:24
-
I copied this into my page where I'm trying to sort out reasonable back/forward behavior for a site with some single page and some intra-page navigation but all I get is 'Travel direction is NaN'. Is there some context assumed here? – Britton Kerin Aug 19 '22 at 01:32
-
Looks like all the NaNs may be something to do with how the the initial getItem result is coerced – Britton Kerin Aug 19 '22 at 01:42
-
And it needs an initial history.pushState somewhere to kick things off (the handler registration is shown so this should maybe also be shown). – Britton Kerin Aug 19 '22 at 01:52