How to implement global loading in astro view transition
View transition has been natively supported by Chrome (most used browser today) since a year ago while Astro supported around 2 years ago. It is a powerful feature that allows for smooth transitions between pages, enhancing the user experience that makes our site feels like Single Page Application (SPA).
However, it is a bit frustrating for users, especially for users in a bad internet connection, when they click a link and nothing happens for a few seconds. To address this issue, we can implement a loading indicator utilizing Astro’s built-in view transitions and events.
Apply View transition
To apply view transition in Astro is pretty simple, import ClientRouter
component then use it in the head of your html layout.
---import { ClientRouter } from "astro:transitions";---
<html> <head> ... <!-- put ClientRouter in the head --> <ClientRouter/> </head> <body> ... </body></html>
It will enable default transition animation for all pages, however if you want to control the transition animation you can use animation directive, details can be found in Astro documentation.
Adding Loading indicator
Now throttle your browser and navigate between pages, you will notice that users will wait for a few seconds before the new page is loaded. So, let’s implement loading indicator using astro’s built-in events.
- astro:before-preparation : this event is triggered when a user clicks a link in your site but content has not loaded yet.
- astro:page-load : this event is triggered when a new page is ready to be displayed (i.e styles and script are loaded).
So what we can do about those events? when astro:before-preparation
we will show a loading indicator then hide the indicator when astro:page-load
is triggered.
Suppose our indicator is a progress bar.
<progress transition:persist id="loading" class="progress progress-primary absolute h-1 w-full top-15 right-0 hidden" value="0" max="100"></progress>
Progress bar should be progressing periodically while waiting for the new page to load.
<script> let loadingBarTimeout: NodeJS.Timeout | undefined = undefined;
function showLoadingBar() { const loadingBar = document.querySelector( "#loading", ) as HTMLProgressElement | null; if (!loadingBar) return;
loadingBar.value = 0; loadingBar.classList.add("animate-pulse"); loadingBar.classList.remove("hidden");
let progress = 0; loadingBarTimeout = setInterval(() => { if (progress < 90) { progress += Math.random() * 10; // Increment by a random value to simulate loading loadingBar.value = progress; } else { clearInterval(loadingBarTimeout!); } }, 200); }
When new page is loaded, make progress bar to be 100% then hide it.
function hideLoadingBar() { const loadingBar = document.querySelector( "#loading", ) as HTMLProgressElement | null; if (!loadingBar) return;
if (loadingBarTimeout) { clearInterval(loadingBarTimeout); }
loadingBar.value = 100; setTimeout(() => { loadingBar.classList.remove("animate-pulse"); loadingBar.classList.add("hidden"); loadingBar.value = 0; }, 300); }
document.addEventListener("astro:before-preparation", showLoadingBar); document.addEventListener("astro:page-load", hideLoadingBar); </script>
putting it together
<progress transition:persist id="loading" class="progress progress-primary absolute h-1 w-full top-15 right-0 hidden" value="0" max="100"></progress>
<script> let loadingBarTimeout: NodeJS.Timeout | undefined = undefined;
function showLoadingBar() { const loadingBar = document.querySelector( "#loading", ) as HTMLProgressElement | null; if (!loadingBar) return;
loadingBar.value = 0; loadingBar.classList.add("animate-pulse"); loadingBar.classList.remove("hidden");
let progress = 0; loadingBarTimeout = setInterval(() => { if (progress < 90) { progress += Math.random() * 10; // Increment by a random value to simulate loading loadingBar.value = progress; } else { clearInterval(loadingBarTimeout!); } }, 200); }
function hideLoadingBar() { const loadingBar = document.querySelector( "#loading", ) as HTMLProgressElement | null; if (!loadingBar) return;
if (loadingBarTimeout) { clearInterval(loadingBarTimeout); }
loadingBar.value = 100; setTimeout(() => { loadingBar.classList.remove("animate-pulse"); loadingBar.classList.add("hidden"); loadingBar.value = 0; }, 300); }
document.addEventListener("astro:before-preparation", showLoadingBar); document.addEventListener("astro:page-load", hideLoadingBar);</script>
And that’s it, now our loading bar should be working smoothly. And actually, you are seeing the implementation of this loading in this site.