feat: directional scroll restoration (only on back navigation)

This commit is contained in:
Julien Maille 2026-01-18 23:30:39 +01:00
parent 80e05b54e1
commit b3b6815ee0

107
js/app.js
View file

@ -958,46 +958,9 @@ document.addEventListener('DOMContentLoaded', async () => {
// Session-based scroll positions (transient, cleared on refresh) // Session-based scroll positions (transient, cleared on refresh)
const scrollPositions = new Map(); const scrollPositions = new Map();
const handleRouter = async (e) => {
// Save scroll position for the previous page if available
if (e && e.oldURL) {
try {
const url = new URL(e.oldURL);
const oldHash = url.hash || '#home';
const content = document.querySelector('.main-content');
if (content) {
scrollPositions.set(oldHash, content.scrollTop);
}
} catch {
// Ignore URL parsing errors
}
}
// Render the new page
await router();
// Restore scroll position for the new page
const newHash = window.location.hash || '#home';
const content = document.querySelector('.main-content');
if (content) {
const savedScroll = scrollPositions.get(newHash);
if (savedScroll !== undefined) {
// Small timeout to ensure DOM layout is stable after render
setTimeout(() => {
content.scrollTop = savedScroll;
}, 0);
}
}
};
// Initial load
await handleRouter(null);
window.addEventListener('hashchange', handleRouter);
// Simple Navigation History
const navStack = [window.location.hash]; const navStack = [window.location.hash];
let navIndex = 0; let navIndex = 0;
let isGoingBack = false;
const updateNavButtons = () => { const updateNavButtons = () => {
const backBtn = document.getElementById('nav-back'); const backBtn = document.getElementById('nav-back');
@ -1006,21 +969,67 @@ document.addEventListener('DOMContentLoaded', async () => {
if (fwdBtn) fwdBtn.disabled = navIndex >= navStack.length - 1; if (fwdBtn) fwdBtn.disabled = navIndex >= navStack.length - 1;
}; };
window.addEventListener('hashchange', () => { const handleRouter = async (e) => {
const hash = window.location.hash; const hash = window.location.hash;
if (hash === navStack[navIndex]) return;
if (navIndex > 0 && hash === navStack[navIndex - 1]) { // 1. Update history state and determine direction
navIndex--; // User went back if (e && e.oldURL) {
} else if (navIndex < navStack.length - 1 && hash === navStack[navIndex + 1]) { try {
navIndex++; // User went forward const url = new URL(e.oldURL);
} else { const oldHash = url.hash || '#home';
navIndex++;
navStack.splice(navIndex); // Truncate forward history // Save scroll position for the old page
navStack.push(hash); const content = document.querySelector('.main-content');
if (content) {
scrollPositions.set(oldHash, content.scrollTop);
}
if (hash !== navStack[navIndex]) {
if (navIndex > 0 && hash === navStack[navIndex - 1]) {
navIndex--;
isGoingBack = true;
} else if (navIndex < navStack.length - 1 && hash === navStack[navIndex + 1]) {
navIndex++;
isGoingBack = false;
} else {
navIndex++;
navStack.splice(navIndex);
navStack.push(hash);
isGoingBack = false;
}
}
} catch {
isGoingBack = false;
}
} }
// 2. Render the new page
await router();
updateNavButtons(); updateNavButtons();
});
// 3. Handle scroll restoration
const content = document.querySelector('.main-content');
if (content) {
if (isGoingBack) {
const savedScroll = scrollPositions.get(hash || '#home');
if (savedScroll !== undefined) {
setTimeout(() => {
content.scrollTop = savedScroll;
}, 0);
}
} else {
// For forward or new clicks, ensure we start at the top
content.scrollTop = 0;
}
}
// Reset flag
isGoingBack = false;
};
// Initial load
await handleRouter(null);
window.addEventListener('hashchange', handleRouter);
updateNavButtons(); updateNavButtons();
audioPlayer.addEventListener('play', () => { audioPlayer.addEventListener('play', () => {