Scroll Position for Astro projects when using the view transitions API
2024-08-08 00:42
When using Astro’s View Transitions API, you might notice that the scroll position isn’t maintained when navigating between pages. This can be particularly noticeable when a user clicks a link, goes to a new page, then uses the browser’s back button. To solve this, I’ve implemented a custom scroll position restoration mechanism on a project.
Just add a script to your Astro layout to handle scroll position restoration:
-
Open your main layout file (often
Layout.astro
or similar). -
Add the following
<script>
tag with theis:inline
directive:
- Alternatively you can create a new Astro component and import it
<script is:inline>
let isBack = false;
let scrollRestoration = false;
document.addEventListener("astro:before-preparation", ({ from, to, direction }) => {
isBack = direction === 'back';
console.log(`Navigation direction: ${direction}`);
});
document.addEventListener("astro:before-swap", () => {
const currentPath = window.location.pathname;
const currentScroll = window.scrollY;
console.log(`Before swap - Current path: ${currentPath}, Scroll position: ${currentScroll}`);
if (!isBack) {
const scrollPositions = JSON.parse(sessionStorage.getItem("scrollPositions") || "{}");
scrollPositions[currentPath] = currentScroll;
sessionStorage.setItem("scrollPositions", JSON.stringify(scrollPositions));
console.log(`Saved scroll position for ${currentPath}: ${currentScroll}`);
} else {
console.log(`Skipped saving scroll position (back navigation)`);
}
});
document.addEventListener("astro:after-swap", () => {
scrollRestoration = true;
const newPath = window.location.pathname;
console.log(`After swap - New path: ${newPath}`);
const scrollPositions = JSON.parse(sessionStorage.getItem("scrollPositions") || "{}");
const savedScrollPosition = scrollPositions[newPath];
console.log(`Retrieved saved scroll position for ${newPath}: ${savedScrollPosition}`);
if (savedScrollPosition !== undefined) {
// Delay scroll restoration
setTimeout(() => {
const element = document.elementFromPoint(0, savedScrollPosition);
if (element) {
element.scrollIntoView();
console.log(`Scrolled to element at position: ${savedScrollPosition}`);
} else {
window.scrollTo(0, savedScrollPosition);
console.log(`Scrolled to position: ${savedScrollPosition}`);
}
scrollRestoration = false;
}, 700);
if (!isBack) {
delete scrollPositions[newPath];
sessionStorage.setItem("scrollPositions", JSON.stringify(scrollPositions));
console.log(`Cleared saved scroll position for ${newPath}`);
}
} else {
console.log(`No saved scroll position found for ${newPath}`);
scrollRestoration = false;
}
});
document.addEventListener("astro:page-load", () => {
console.log(`Page fully loaded: ${window.location.pathname}`);
});
// Prevent scroll events during transition
window.addEventListener('scroll', (e) => {
if (scrollRestoration) {
e.preventDefault();
window.scrollTo(0, 0);
}
}, { passive: false });
</script>
Here’s a breakdown of what the code does:
- Uses
sessionStorage
to store scroll positions for each page. - Captures scroll position before navigation using
astro:before-preparation
event. - Restores scroll position after page transition using
astro:after-swap
event. - Implements a delay before scroll restoration for improved reliability.
- Handles both element-based and pixel-based scroll restoration.
- Clears saved scroll positions after restoration to maintain cleanliness.
- Prevents scroll events during transition to avoid visual glitches.
- Logs various steps for debugging purposes.