I have a single page website and would like to achieve the following:
back button working as if it was a normal website
and instead of say,
In principle, there is no great problem with this as long as you work with the existing storage object (a stack) for previously visited web pages on your browser. This object is the history object and you can see what is in it anytime by right-clicking and selecting "Inspect", then selecting the "Console" tab, then enter window.history and return.
I refer you to the Browser Object Model (BOM) section of Pro Java For Web Developers (Frisbee) for the background to the history object. (Just a few pages, an easy read, don't worry.) Just remember that in this process you are storing the new page that you move to, not the old page that you are leaving !
For a simple SPA example, look at this CodePen example: https://codepen.io/tamjk/pen/NWxWOxL?editors=0010
HTML
LOGO
THIS IS GREEN PAGE FULL OF STUFF
CSS
body{
text-align: center;
margin: 0 auto;
height: auto;
font-family: Open Sans, sans-serif;
font-size: 1.4rem;
color: #000;
background-color: orange;
}
header{
display: grid;
height: 100px;
grid-template-columns: 100px 100px calc(100% - 200px);
grid-template-rows: auto;
grid-template-areas: "logo . nav";
justify-items: space-between;
align-items: center;
align-content: space-between;
margin: 0 2.5% 50px 2.5%;
height: 100px;
font-weight: bold;
text-align: left;
}
.logo {
grid-area: logo;
background-color: #FF00FF;
display: flex;
justify-content: center;
align-items: center;
align-content: center;
width: 100px;
height: 80px;
}
nav{
grid-area: nav;
display: block;
position: relative;
list-style-type: none;
width: calc(100% - 100px);
height: 80px;
text-align: right;
}
.navbar{
display: flex;
justify-content: flex-end;
align-items: center;
align-content: center;
height: 80px;
list-style-type: none;
text-align: justify;
font-size: 14px;
margin: 0px;
padding: 0px;
}
.cont{
width: auto;
height: 100px;
}
nav li {
position: relative;display: flex;
justify-content: flex-end;
align-items: center;
align-content: center;
margin-left: 150px;
width: auto;
height: 40px;
font-weight: bold;
line-height: 40px;
text-align: center;
color: #FFFFFF;
cursor: pointer;
}
nav li::after {
position: absolute;
display: block;
bottom: 0;
left: 0;
right: 0;
margin: auto;
content: '';
height: 2px;
width: 0;
opacity: 0;
transition: height 1300ms, opacity 1300ms, width 1300ms, transform 1300ms;
}
.cont:not(.on):hover::after {
width: 100%;
height: 2px;
opacity: 1;
}
.cont:not(.on):hover{
border-bottom: solid 3px #0000FF;
}
.on:not(.logo) {
border-bottom: solid 3px #00CCFF;
}
.on {
cursor: none;
pointer-effects: none;
}
article{
width: 95%;
margin: 0 auto;
}
.band{
height: 800px;
font-weight: 600;
font-size: 4rem;
color: gold;
text-align: center;
display: flex;
justify-content: center; /* align horizontal */
align-items: center; /* align vertical */
}
.hidden{
display: none;
}
.greenpage {
background-color: #00FF00;
}
.redpage{
background-color: #FF0000;
}
.whitepage{
background-color: #FFFFFF;
}
.bluepage{
background-color: #0000FF;
}
footer{
margin: 0 auto;
width: 95%;
text-align: center;
height: auto;
background: #000;
}
.foot-cont{
height: 100px;
font-size: 1.4rem;
font-style: italic;
color: #FFF;
display: flex; /* Important here, heh-heh */
justify-content: center; /* align horizontal */
align-items: center; /* align vertical */
}
JS
const PAGE = ( () =>
{
// Switches the displayed page in a single-page application website
// param: newlinkid - the id of the link to the new page on the navbar
// param: newpageid - the id of the page content within the element
// param: hist - a boolean variable, true if a page change is via the BACK button, false otherwise
const switchPage = function(newlinkid, newpageid, hist)
{
const oldLink = document.getElementsByClassName("on")[0];
let oldLinkId = oldLink.id; // Find current nav link element
let oldpageid; // Get id of corresponding page block
if (oldLinkId === "logo")
{
oldpageid = "greenpage";
}
else // To get oldpageid ...
{
oldpageid = oldLinkId.substring(0, oldLinkId.length - 4) + "page"; // ... strip off "link" and append "page"
}
const oldPage = document.getElementById(oldpageid); // Find container for old ...
const newPage = document.getElementById(newpageid); // ... and new pages
if (oldLinkId !== newlinkid) // Only change when clicking on different page
{
const newLink = document.getElementById(newlinkid); // Put indicator on logo ...
newLink.classList.add("on"); // Show new nav link as active page
newLink.style.cursor = 'none'; // Disable pointer on current link
oldLink.classList.remove("on"); // Remove active status from old nav link
oldLink.style.cursor = 'pointer'; // Enable pointer on old link
oldPage.classList.add("hidden"); // Hide old page content
newPage.classList.remove("hidden"); // Display current page content
if (!hist)
{
addToHistory(newlinkid, newpageid); // Add current page to browser's history stack
}
}
};
// Update browser's history object ...
// param: newlinkid - the id of the link to the new page on the navbar
// param: newpageid - the id of the page content within the element
const addToHistory = (newlinkid, newpageid) =>
{
let history, // Window history object ...
stateObject, // and page state object ...
currpage, // Variables for current (new) page ...
title; // ... title ...
if (newpageid != 'greenpage')
{
currpage = newpageid.substring(0, newpageid.length - 4); // Get page name by knocking off "link" from page id
title = "YourSPASite" + " | " +
currpage[0].toUpperCase() + currpage.slice(1); // Make title by uppercasing first page char
document.title = title;
}
else
{
currpage = 'home';
title = "YourSPASite";
document.title = title;
}
stateObject = { "currlinkid": newlinkid,
"currpageid": newpageid };
history = window.history;
history.pushState(stateObject, title);
};
// Overrides default callback for popstate event on clicking browser's BACK button
window.addEventListener("popstate", (event) =>
{
let state = event.state;
if (state) // Where a state object exists ...
{
switchPage(state["currlinkid"], state["currpageid"], true);
}
});
return { switchPage: switchPage };
})();
pageLoad = () =>
{
PAGE.addToHistory('logo', 'greenpage', false); // Add opening page to browser history on loading
}
In regard to the URL, the method that the history object uses to load a new page state into the history stack, i.e. pushState(...), has an optional third parameter for associating a dummy URL for each web page that is stored. Personally, when I first sorted out the BACK & FORWARD functions, I did not use dummy URLs as the browser was being confused by them and I had enough to do sorting out the history sequence using just the first two parameters, i.e.
In the example above, for the state object I just used the IDs of the page's nav link and its content element. For the title, I programmatically changed the HTML's page content with each change of page. I did this after noticing that the browser listed the previous pages according to the element in the HTML code. Unfortunately, this title does not show up on CodePen when you right-click on the browser BACK and FORWARD buttons. That is due to CodePen's system not allowing it. But it will show on your own sites.
I expect that you could also use a dummy URL but I will leave that to the student as an exercise, as they say.
It's important that whatever method you use to store current web page states when using the navbar links to navigate, you DO NOT ADD page states to the browser history when you arrive at them using BACK or FORWARD buttons. Otherwise your history stack will have repetitions of entries going back and deletion of entries going forward.
In the CodePen, this was achieved by having the AddToHistory(..) function separate to and outside the scope of the switchPage(...) function. This allows you use of the switchPage function in both normal navbar navigation and browser BACK/FORWARD navigation. The third parameter of switchPage(...) is a boolean indicating if the page is to be stored in history or not.
Anyway, this is just something to get you started.