New loading bar with Sveltekit

Published

Table of contents

Background

A little while ago, I found this nice tutorial on how to make a loading bar in SvelteKit. I adapted it for my own use, and it works great. However, SvelteKit now has new functions beforeNavigate and afterNavigate, and I refactored the loading bar to make it clearer, cleaner, and more self-contained.

In this tutorial, we'll make a loading bar Svelte component that can be dropped into any SvelteKit project from v1.0.0-next.227 and up (unless something changes again).

Note: This loading bar is completely fake in the fact that it has nothing to do with the page actually loading, but it makes a big difference in the user experience.

How to

In the top-level script tag in the LoadingBar.svelte file, beforeNavigate and afterNavigate can be imported from $app/navigation:

import { afterNavigate, beforeNavigate } from '$app/navigation';

We want the loading bar to only show when the page is in the process of loading, so we'll initialize a variable to control whether or not to show it:

let showLoadingBar = false;

Next, we'll set up a progress variable using Svelte's built-in tweened function, imported from svelte/motion.

import { tweened } from 'svelte/motion';
import { cubicOut } from 'svelte/easing';

const progress = tweened(0, {
	duration: 3500, // The time it takes to move between values
	easing: cubicOut, // The easing function to use
});

If you're not familiar with Svelte's tweened function, this sets up a value at zero that will take 3500 ms, using the cubicOut easing function, to get from zero to whatever value we set it to next (and then to the next value, and so on). Of course, you can use any easing function you want, or even write your own.

Next, let's look at the markup and styles for the loading bar.

{#if showLoadingBar}
	<div class="progress-bar">
		<div class="progress-sliver" style="width: {$progress * 100}%" />
	</div>
{/if}

This is fairly simple. It should only be rendered when showLoadingBar is true, there's a wrapper/background div (progress-bar) and another div showing the progress.

Here are the styles (these go in the style tag in the LoadingBar component):

.progress-bar {
	 position: fixed;
	 will-change: transform; /* Helps with position fixed supposedly */
	 top: 0;
	 width: 100%;
	 height: 0.5ch;
	 background: #B8B8B8 ;
	 z-index: 2000;
 }

 .progress-sliver {
	 background: #A11208 ;
	 height: 100%;
 }

The progress-bar div is fixed at the top of the screen, full width, and 0.5ch tall, and a light gray background. I gave it a high z-index because I want it on top of everything except for modals and such, but that's entirely optional.

The progress-sliver div is simply set to 100% height, and I picked a nice dark red for this demo. Remember, the progress width is set with the inline style attribute so we can control it with JavaScript.

Finally, let's make it move. Make sure to import beforeNavigate and afterNavigate.

beforeNavigate(() => {
	showLoadingBar = true;
	progress.set(0.7);
});

afterNavigate(() => {
	progress.set(1, { duration: 500 });

	setTimeout(() => { // Hide and reset to zero after it finishes animating
		showLoadingBar = false;
		progress.set(0, { duration: 0 });
	}, 600);
});

Before the page starts navigating, we show the loading bar and set the progress to 70%, or 0.7. Because we set the duration on progress to 3500, it'll take 3.5 seconds to get from zero to 70%. If the page hasn't loaded by then, the loading bar will just hang at 70%.

After the page has finished navigating, we set the progress to 100% or 1, and specify a duration of 500 milliseconds, so it's much faster than getting to 70%. Then, we use setTimeout to hide the loading bar again after it's finished animating. I found that a delay of 600 ms with the duration of 500 ms looks pretty good, but feel free to mess around with it and try your own values. We also reset the progress to zero with a duration of zero, so it happens immediately.

That's it! If you want a site-wide loading bar, just import and use your loading bar component in your __layout.svelte file. If the z-index works for you, it shouldn't matter where you put the loading bar in the markup.

Example

Here's a demo with the loading bar implemented.

Conclusion

As I wrote earlier, this is basically just a refactoring of Shajid Hasan's Sveltekit loading bar, taking advantage of newer features to make it self-contained. No external stores, and no event listeners in other files. Just the loading bar component that does its thing every time you load a page. Except for the first time, before Sveltekit's routing takes over. I think this is actually better than having this loading bar be active on first load, because then you would have two loading indicators, which could be confusing.

Code

Here's the full code if you just want to copy and paste.

<script>
	import { afterNavigate, beforeNavigate } from '$app/navigation';
	import { tweened } from 'svelte/motion';
	import { cubicOut } from 'svelte/easing';
	
	let showLoadingBar = false;
	
	const progress = tweened(0, {
		duration: 3500, // The time it takes to move between values
		easing: cubicOut, // The easing function to use
	});
	
	beforeNavigate(() => {
		showLoadingBar = true;
		progress.set(0.7);
	});
	
	afterNavigate(() => {
		progress.set(1, { duration: 500 });
	
		setTimeout(() => { // Hide and reset to zero after it finishes animating
			showLoadingBar = false;
			progress.set(0, { duration: 0 });
		}, 600);
	});
</script>

{#if showLoadingBar}
	<div class="progress-bar">
		<div class="progress-sliver" style="width: {$progress * 100}%" />
	</div>
{/if}

<style>
	.progress-bar {
		 position: fixed;
		 will-change: transform; /* Helps with position fixed supposedly */
		 top: 0;
		 width: 100%;
		 height: 0.5ch;
		 background:  #B8B8B8 
		 z-index: 2000;
	 }
	
	 .progress-sliver {
		 background:  #A11208 
		 height: 100%;
	 }
</style>

Article closing

#article #coding/svelte #coding/sveltekit #writing #coding/js Articles

More posts: