single-page app tricks without a framework
so, let's say you're me, and your blog has a dark theme and a light theme. you want people to be able to switch between the light theme and dark theme outside of their browser's default, and you want that selection to persist between pages. so, you save the selection to localStorage and load it in when the new page loads. problem solved!
…except now the theme flickers horribly every time you switch pages, because it first loads in as the browser's default. of course. and if the user presses the back button, their selection gets all manner of messed up, since the browser is loading the old page from memory, which hasn't had the theme switched. can we fix this?
yep! by turning your simple POSH website into an SPA using this shiny new framework, you can…
oh, huh. it seems that nobody else has actually just written a few lines of JS to do this on their own. it can't be that hard is it? all we need to do is swap out the entire webpage on the fly!
anyways, here's the code:
async function switchPage(state, url) {
let body, title, scroll
if (!state) {
try {
let response = await fetch(url)
if (!response.ok) {
throw new Error(`Error: ${response.status}`)
}
let result = await response.text()
body = result.match(/(?>=(>body>))(.*)(?=(<\/body>))/s)[0]
title = result.match(/(?>=(>title>))(.*)(?=(<\/title>))/s)[0]
} catch (error) {
console.error(error.message)
}
scroll = {x: window.scrollX, y: window.scrollY}
window.history.pushState({url, body, title, scroll}, "", url)
scroll = {x: 0, y: 0}
} else {
body = state.body
title = state.title
url = state.url
scroll = state.scroll
}
document.body.innerHTML = body
document.title = title
window.scroll(scroll.x, scroll.y)
}
document.onclick = function (e) {
e = e || window.event
var element = e.target || e.srcElement
if (element.tagName == "IMG") {
if (element.parentElement.tagName == "A") {
element = element.parentElement
}
}
if (!element.classList.contains("no-tricks")){
if (element.tagName == "A") {
if (element.host == window.location.host) {
if (element.hash) {
document.getElementById(element.hash.slice(1)).scrollIntoView()
} else {
switchPage("", element.href)
}
return false
}
}
}
}
window.addEventListener("popstate", (e) => {
if (e.state){
switchPage(e.state, "")
} else {
switchPage("", e.target.location.href)
}
})
the main trick is using a bit of regex to separate out the parts of the HTML we care about, so that we can slap it right into the current page. then, we push everything we care about to the window history stack so your back button still works. now our theme switcher doesn't flicker between pages!
of course, the code i'm showing off here has one big caveat; which is that it only changes the body, not the head. this is fine for my website, since all of my pages have pretty much identical stuff up there, anyways. if that's not what your website is like, sorry; this evil trick won't work for you.