⮌ back | Overview | Home

The Loading Indicator

The <ProgressBar /> and <LoadingIndicator /> components offer loading indicators for your view transitions. To see them in action, visit the ProgressBar page or the LoadingIndicator page and press the “Go to page two” link.

There is also a demo for a crazily styled <ProgressBar /> here, which can be started by pressing on “Showtime”.

Contents

Why should I use this component?

The loading indicator is a great way to let your users know that something is happening. For view transitions, this is even more important than for normal page loads, as the view transition animation only starts after the target page has fully loaded. With a slow internet connection or heavy server-side rendering, this can take quite a while. From the instant the user clicked the link up to the start of the view transition animation the user typically gets no visual feedback. This gap is now filled by the loading indicator.

How does it work?

The Bag offers very versatile support for loading indicators. The common feature is to start an action with the click on the link and stop it again when the target page is fully loaded.

To achieve this, <LoadingIndicator/> inserts a loading class into the html element of the page and removes it later. You can also have it call your Javascript code to show and hide something.

Without any further configuration, the LoadingIndicator component displays a pulsating version of your favicon in the top right corner of the screen during loading.

Usage

In good Astro tradition this component works out of the box without any configuration. But if you want you can also customize it completely to your needs.

Alternative 1: Use it as an Astro integration

The easiest way to enjoy the loading indicator is to install astro-vtbot as an Astro integration. This will automatically add the <LoadingIndicator /> component to all pages that use the <ViewTransitions/> component.

If you want to keep the integration, e.g. for the automatic linter support, but want to (temporarily) disable the loading indicator, set loadingIndicator to false in the configuration:

astro.config.mjs
integrations: [vtbot({ loadingIndicator: false })]

Favicon image

You should have a <link rel="icon" href="/favicon.xyz"> in your page <head> element. This will be used as the image for the loading indicator. If you do not have a favicon link, <LoadingIndicator> will try to load the standard /favicon.ico. If that is not there, it will show a grey circle as loading indicator.

<LoadingIndicator/> supports various image formats but it works best with raster images with a minimal resolution of 128 x 128 or with .svg files. It works not so well with small .ico files.

The image is contained in a <div> element which has the id vtbot-loading-indicator and you might apply your own styling to it. The generates code will look like this

your-page.html
<div id="vtbot-loading-indicator">
<img src="/favicon.png" alt="Loading indicator" />
</div>

or

your-page.html
<div id="vtbot-loading-indicator">
<svg>...</svg>
</div>

Alternative 2: Use the <LoadingIndicator /> or the <ProgressBar /> component

If you do not install astro-vtbot as an Astro integration, or if you set loadingIndicator to false in your Astro configuration, you can still install the <LoadingIndicator /> component directly on your page. The <LoadingIndicator /> component should be put inside the <head> element behind the <ViewTransitions/> component.

YourLayout.astro
---
import { ViewTransitions } from 'astro:transitions'
import LoadingIndicator from 'astro-vtbot/components/LoadingIndicator.astro'
---
<html>
<head>
<ViewTransitions />
<LoadingIndicator />
...
</head>
<body>
...
</body>
</html>

Without further configuration, the <LoadingIndicator /> component will display a pulsating version of your favicon in the top right corner of the screen during loading.

You can configure the source of the image as well as the position. These is the interface:

LoadingIndicator.astro
---
export interface Props {
top?: string;
bottom?: string;
left?: string;
right?: string;
src?: string;
}
---

The src attribute is a href, the position strings are values for the corresponding CSS attributes, e.g.

<LoadingIndicator bottom="3vh" left="3vh" src="/some/other.png"/>

As long as you include the <LoadingIndicator /> component behind the <ViewTransitions /> in the <head> its attributes will override the defaults of the <LoadingIndicator /> component that is installed by the Astro integration. So it is ok to have the default via integration on most pages and override it on some pages with an explicit <LoadingIndicator> element.

The <ProgressBar /> is used the same way.

YourLayout.astro
---
import { ViewTransitions } from 'astro:transitions'
import ProgressBar from 'astro-vtbot/components/ProgressBar.astro'
---
<html>
<head>
<ViewTransitions />
<ProgressBar />
...
</head>
<body>
...
</body>
</html>

There are no properties yet to control the behavior of the <ProgressBar />. But you can style its appearance wih CSS.

Styling the <ProgressBar /> with CSS

The <ProgressBar /> component is implemented using Swup’s progress bar plugin. Thus styling is straight forward:

{/* You can style the progress bar by defining the swup-progress-bar class */}
<style is:global>
.swup-progress-bar {
background-color: rgb(64, 128, 192);
}
</style>

Styling the <LoadingIndicator /> with CSS

As soon as the user clicks the link, the CSS class loading is added to the body element of the page. Once the target page is loaded completely and the view transition is about to begin, the loading CSS class is removed again. This allows you to customize the loading indicator via CSS.

The default CSS covers positioning, sizing, and pulsing of the indicator.

LoadingIndicator.astro
<style is:global>
@layer vtbot {
@keyframes vtbot-blink {
0%, 30% { opacity: 0.0; }
40% { opacity: 0.5; }
50%, 60% { opacity: 0.0; }
80% { opacity: 0.3; }
}
#vtbot-loading-indicator {
position: fixed;
top: 3vh;
right: 3vw;
width: min(10vw, 10vh);
z-index: 1000;
opacity: 0;
}
#vtbot-loading-indicator img {
width: min(10vw, 10vh);
height: auto;
}
html.loading #vtbot-loading-indicator {
animation: vtbot-blink 1s infinite;
}
}
</style>

You can override it with your own styling. Best way to do this is to define your own custom loading indicator.

Trick: The animation defined above runs a one second cycle over and over. It keeps the loading indicator invisible for the first 30% of the cycle. I.e. for the first 300 ms your indicator does not show. Or in other words: Even though the .loading class is added immediately after the user clicks the link, the indicator will not appear for the first 300 ms. This is a good thing, as it only shows the indicator for slow loading pages and not for a page that is already in the browser cache.

Use your own #vtbot-loading-indicator

If you want to use your own loading indicator element, you can do so by adding an HTML element with the ID vtbot-loading-indicator to your page. In this case the <LoadingIndicator/> component does not insert its own loading indicator. Again, best way to do this is to define your own custom loading indicator.

Using CSS to define your custom loading indicator

You can define a custom loading indicator by extending the <LoadingIndicator /> component. Typically, your component will define an HTML element and styling. The logic for showing and hiding the loading indicator will be handled by the <LoadingIndicator /> component.

Here is the template that you also find in the astro-vtbot package.

components/template/LoadingIndicatorTemplate_CSS.astro
---
import LoadingIndicator from 'astro-vtbot/components/LoadingIndicator.astro'
---
{/* By rendering <LoadingIndicator/> here,
you inherit the logic that sets the "loading" CSS class */}
<LoadingIndicator />
{/* Defining this div is only necessary
if you do not want to use the default element that holds the favicon image */}
<div id="vtbot-loading-indicator"> <!-- make sure to use this id -->
...
<div>
<style is:global>
#vtbot-loading-indicator {
/* basic styling, typically with position: fixed */
/* hide the indicator with display: none, opacity: 0, visibility: hidden, right: -100vw, ... */
...
}
html.loading #vtbot-loading-indicator {
/* show the indicator with display: block, opacity: 1, visibility: visible, right: 3vw, ... */
...
}
</style>

If you are happy with the default loading indicator html you can keep it. Otherwise you define your own, see line 10 above and the section on alternative elements.

Using JavaScript to define your custom loading indicator

You do not want to craft animations solely with CSS and prefer to use some JavaScript library like GSAP et.al? No problem. Also in that case you can craft your custom loading indicator. Instead of <LoadingIndicator/> component use the loading-indicator.ts script.

Here is the template that you also find in the astro-vtbot package.

components/template/LoadingIndicatorTemplate_JS.astro
---
// Just define javascript or typescript code for hide & show functions
// in the script below.
// You also have to provide the HTML for the loading indicator.
// Include it on your pages, preferable via a common layout
// or - if it is simple - generate it dynamically in the `show` function.
// If you have time consuming setup tasks, you should not run them in `show`.
// It is better to move them to the optional `initializer` function,
// which is executed shortly after a page has been loaded,
// and not when the indicator is already to be displayed
// You can generate the default loading indicator of the back of tricks
// by calling `vtbotLoadingIndicator()`, preferably in the `initializer` function.
---
<script>
import { loading } from 'astro-vtbot/components/loading-indicator';
loading(show, hide, initializer);
function show() {
// show loading indicator
}
function hide() {
// hide loading indicator
}
function initializer() {
// this function is optional
// remove the init parameter in the call to loading above if you do not need it
}
</script>

You just define the two functions show and hide and tell the loading-indicator about them. In the body of these two functions you can craft your special effects!

Using custom loading indicators

Your custom loading indicator components can then be used instead of <LoadingIndicator>. No need anymore for the <LoadingIndicator /> component in the <head>. Whether your custom component needs to be inserted into the head or the body depends on the HTML elements the template section of your component directly inserts into the DOM:

YourPage.astro
---
import { ViewTransitions } from 'astro:transitions'
import MyCustomLoadingIndicator from './MyCustomLoadingIndicator.astro'
---
<html>
<head>
<ViewTransitions />
...
</head>
<body>
<MyCustomLoadingIndicator />
...
</body>
</html>

<BrakePad/>: Slowing things down during development 🦥

Testing loading indicators is hard with your slick local server. Transitions happen in no time and you can not even get a glimpse of your flashy indicator? You can use your browser’s developer tools to simulate a slow network connection!

But you can also put the brakes on, with the <BrakePad/> from the Bag of Tricks.

Publish or contribute your custom loading indicator! (Pocycli!)

I’m curious to see what you will build!

If you define your own custom loading indicator - either by extending <LoadingIndicator/> or by defining show & hide with loading-indicator.ts as described above: Please drop me a line (issue or pull request at https://github.com/martrapp/astro-vtbot-website, or DM @Martin at Astro’s Discord server) and I will gladly link to your extension or you add it as a contribution to the 👜 Bag of Tricks. ✨