Detecting when the browser's "back" and "forward" buttons are pressed.
Table of Contents
-
- 3.1. Back
- 3.2. Forward
- 3.3. New Page Transition
- 3.4. None of the above
Introduction
How can we detect when a page transition occurs using the browser's "back" and "forward" buttons?
While there is a method using the popstate event 1, popstate does not occur during normal page transitions (moving to a different page). It is only meaningful when managing history with pushState within the same page. When moving to a different page, the previous page is destroyed, and thus all event handlers, including popstate, are also destroyed. Furthermore, information to determine whether it was a "back" or "forward" action is not provided.
There is also a method using performance.navigation.type 2. This can be used for transitions to different pages, but if HTML caching is disabled, the type value will always be 1: TYPE_RELOAD, making it indistinguishable from a normal page transition. Even when caching is enabled, the type value for "back" and "forward" is 2: TYPE_BACK_FORWARD, so, like popstate, no information is provided to determine whether it was a "back" or "forward" action. Furthermore, performance.navigation.type has been deprecated, so browsers may stop supporting it in the future.
Here, we introduce a method that combines sessionStorage and history.state 3.
The differences between each method are as follows.
| Method | Back/Forward Detection | When Cache is Disabled | Notes |
|---|---|---|---|
popstate |
✓ | ✗ 4 | For SPAs |
performance.navigation.type |
✓ (Old spec) | ✗ (Always reload) | Deprecated |
sessionStorage + history.state |
◎ | ◎ | For normal page transitions |
Code
- Normal page transition
- Transition by back button
- Transition by forward button
- None of the above
The code to determine these is as follows.
window.addEventListener('pageshow', function () {
// Get the timestamp from sessionStorage before the transition
const prevTimestamp = Number(sessionStorage.getItem('prevTimestamp') ?? -1);
// Get the timestamp saved in history.state
let currentTimestamp = history.state?.timestamp ?? null;
// Determine if it's a new page transition
if (!currentTimestamp) {
console.log('新しいページ遷移'); // New page transition
currentTimestamp = Date.now();
if (currentTimestamp === prevTimestamp) {
currentTimestamp += 1; // I don't think the timestamps will be the same, but just in case
}
// Save timestamp to history.state
history.replaceState({
timestamp: currentTimestamp
}, '');
} else {
console.log('履歴内の移動'); // Movement within history
if (prevTimestamp > currentTimestamp) {
console.log('戻るボタン'); // Back button
} else if (prevTimestamp < currentTimestamp) {
console.log('進むボタン'); // Forward button
} else {
console.log('どれでもない'); // None of the above
}
}
sessionStorage.setItem('prevTimestamp', currentTimestamp);
});
Explanation
For example, let's consider a case where page transitions occur as follows.
| History Order | Timestamp | Page |
|---|---|---|
| 1 | 15:00 | page1 |
| 2 | 15:10 | page2 |
| 3 | 15:20 | page3 |
| 4 * | 15:30 | page1 |
* indicates the currently displayed page.
Since a timestamp is set in history.state for each new page transition, it looks like this:
| History Order | history.state.timestamp | Page |
|---|---|---|
| 1 | 15:00 | page1 |
| 2 | 15:10 | page2 |
| 3 | 15:20 | page3 |
| 4 * | 15:30 | page1 |
The prevTimestamp stored in Session Storage is 15:30.
Back
If you navigate with the "back" button here, you move to the 3rd page, and history.state.timestamp is 15:20.
prevTimestamp(15:30) > history.state.timestamp(15:20)
So we know it's a "back" action.
| History Order | history.state.timestamp | Page |
|---|---|---|
| 1 | 15:00 | page1 |
| 2 | 15:10 | page2 |
| 3 * | 15:20 | page3 |
| 4 | 15:30 | page1 |
prevTimestamp is updated to 15:20.
Forward
Next, if you navigate with the "forward" button, you move to the 4th page, and history.state.timestamp is 15:30.
prevTimestamp(15:20) < history.state.timestamp(15:30)
So we know it's a "forward" action.
| History Order | history.state.timestamp | Page |
|---|---|---|
| 1 | 15:00 | page1 |
| 2 | 15:10 | page2 |
| 3 | 15:20 | page3 |
| 4 * | 15:30 | page1 |
prevTimestamp is updated to 15:30.
New Page Transition
Now, let's try a normal page transition instead of "back" or "forward". Suppose you click a link within the page or directly type a URL into the browser's address bar to display page3.
At this time, a new entry is added to the history.
Since it's a new entry, history.state contains nothing.
Therefore, we know it's a "new page transition".
Prepare for the next determination by setting the current timestamp in history.state.
| History Order | history.state.timestamp | Page |
|---|---|---|
| 1 | 15:00 | page1 |
| 2 | 15:10 | page2 |
| 3 | 15:20 | page3 |
| 4 | 15:30 | page1 |
| 5 * | 15:40 | page3 |
prevTimestamp is updated to 15:40.
None of the above
Let's perform a normal page transition again. We'll navigate to page3 in the same way as before.
Alternatively, reloading will result in the same behavior.
At this time, no new entry is added to the history.
history.state.timestamp is set to 15:40.
prevTimestamp(15:40) = history.state.timestamp(15:40)
This is the "none of the above" pattern. The history state does not change.
| History Order | history.state.timestamp | Page |
|---|---|---|
| 1 | 15:00 | page1 |
| 2 | 15:10 | page2 |
| 3 | 15:20 | page3 |
| 4 | 15:30 | page1 |
| 5 * | 15:40 | page3 |
prevTimestamp remains 15:40.
It seems there is a browser history specification that states: if you display a page with the same URL as the currently displayed page, the history does not increase. This applies not only to the beginning of the history; if you re-display the same page while page1 or page2 is showing, the history will not increase.
While I used the phrase "none of the above," "transitioned to the same page" might be a more appropriate expression.
Notes
pageshowevent 5
When a page is restored from cache, theloadevent does not occur, sopageshowis used.
history.state
In the code example,stateis overwritten withhistory.replaceState, but ifstateis being used for other purposes, care must be taken to avoid mutual overwriting.sessionStorage
SincesessionStorageis data shared only within the same origin and same tab, it cannot be used for detection across different origins.
Footnotes
-
If caching is disabled, navigating with the "back" or "forward" buttons will cause a reload, and thus the
popstatehandler will be destroyed.↩