How to create a progress indicator in a Svelte application

Why do we need a progress indicator?

Progress indicators in user interface design are often overlooked, but they have a significant impact on the user experience. They inform and engage users during tasks such as file downloads, content uploads, or process completion. Designing an effective and captivating progress indicator requires careful consideration.

In this Educative Answer, we will explore how to create a progress indicator in a Svelte application that specifically focuses on file uploads. As the upload progresses, we’ll display a progress indicator to show the user how far along they are in the upload process.

Note: We won’t go into the details of how to develop a Svelte application from scratch and are using the default template to display our progress indicator module. However, if you are interested in learning more about Svelte, you can find more information on the topic in our Building Reactive Apps with Svelte and Tailwind course.

The following screenshot shows what our application would look like:

Application interface
Application interface

Progress indicator implementation in Svelte

We will explore the implementation details of the progress bar in a Svelte application.

We’ll examine the HTML structure and the JavaScript logic that work together to create a dynamic and visually appealing progress indicator.

By understanding these components, we’ll be able to integrate a progress bar into our own Svelte applications and enhance the user experience during file uploads or other relevant tasks.

HTML structure

The HTML structure for the progress bar in the Svelte application consists of the following elements:

  • Completion message container: This container, represented by the <div id="msg-cont"> element in our code, is responsible for displaying the completion message when the progress reaches 100%100\%. It wraps the completion message element <h3 id="completion-msg">.
<div id="msg-cont">
{#if barWidth === 100}
<h3 id="completion-msg" in:fly={{x: -300}} out:fly={{x: 300}}>
The file has been uploaded!
</h3>
{/if}
</div>
  • Progress bar container: The <div id="progress"> element acts as the container for the progress bar and percentage display. It controls the visibility of these elements based on the isActive variable.
<div id="progress">
{#if isActive}
<span>{barWidth}%</span>
<div id="myBar" style="width: {barWidth}%"></div>
{/if}
</div>
  • Upload form: The <form> element encapsulates the file upload functionality. It includes an <input type="file"> element for selecting the file, along with an “Upload File” button and a “Cancel” button. The makeProgress and resetBar functions handle the form submission and cancellation, respectively.
<form on:submit|preventDefault={makeProgress}>
<input type="file" id="myFile" name="filename" bind:this={chooseFileBtn}>
<button disabled={isActive}>Upload File</button>
<button on:click|stopPropagation|preventDefault={resetBar}>Cancel</button>
</form>

This HTML structure defines the layout and functionality of the progress bar in the Svelte application.

JavaScript structure

The JavaScript structure for the progress bar in the Svelte application consists of the following elements:

  • Variables: The following code defines several variables using the let keyword:

    • barWidth: Tracks the current progress of the upload, initially set to 0.

    • progress: Stores the interval reference for updating the progress bar.

    • isActive: Indicates whether the progress bar is active or not.

    • chooseFileBtn: Stores a reference to the file input element using the bind:this directive.

let barWidth = 0;
let progress; // for setInterval
let isActive = false;
let chooseFileBtn; // use with bind:this and the input type="file"
  • Reactive statement: The code utilizes a reactive statement to observe changes in the barWidth variable. When barWidth reaches 100100, the reactive statement triggers two actions:

    • stopProgress(): Calls the stopProgress function to clear the interval and reset the input value after a delay of 2500ms.

    • resetBar(): Calls the resetBar function to reset the progress bar to its initial state.

$: if (barWidth === 100) {
stopProgress();
setTimeout(resetBar, 2500);
}
  • Functions

    • stopProgress(): This function stops the progress by clearing the interval and resetting the input value. It is invoked when the upload is completed or canceled.

    • resetBar(): This function resets the progress bar by setting barWidth to 0 and calling stopProgress().

    • addColor(): This function increments the barWidth variable by 1, simulating progress. It is called repeatedly using setInterval during active progress.

    • randomTime(): This function generates a random time value between 0 and 60, used as the interval duration for updating the progress bar.

    • makeProgress(): This function is triggered when the upload is initiated. It first checks if a file has been chosen using chooseFileBtn.value. If a file is chosen, it sets isActive to true and calls setInterval with addColor and randomTime to start updating the progress bar.

const stopProgress = () => {
clearInterval(progress);
isActive = false;
chooseFileBtn.value = "";
}
const resetBar = () => {
stopProgress();
barWidth = 0;
}
const addColor = () => barWidth += 1;
const randomTime = () => Math.floor(Math.random() * 60)
const makeProgress = () => {
if (!chooseFileBtn.value) {
alert("Choose a file")
} else {
isActive = true;
progress = setInterval(addColor, randomTime());
}
}

It provides the necessary functions to start, stop, and reset the progress and simulate progress updates using intervals.

The Svelte application

We can run our application by pressing the “Run” button in the widget below:

Note: There is some supplementary code included alongside the HTML and JS that we previously discussed. However, it is important to note that the code responsible for creating the progress bar remains largely unchanged, just as we discussed.

<script>
  import { fade, fly } from 'svelte/transition';
  let barWidth = 0;
  let progress; // for setInterval
  let isActive = false;
  let isLoading = true; // added variable
  let chooseFileBtn; // use with bind:this and the input type="file"
  let showCompletionMsg = false; // added variable
  let completionMsgVisible = false;

  const stopProgress = () => {
    clearInterval(progress);
    isActive = false;
    chooseFileBtn.value = "";
  };

  const resetBar = () => {
    stopProgress();
    barWidth = 0;
    completionMsgVisible = false;
  };

  const addColor = () => {
    if (barWidth < 100) {
      barWidth += 1;
    } else {
      stopProgress();
      showCompletionMsg = true;
      setTimeout(() => {
        showCompletionMsg = false;
        resetBar();
      }, 3000);
    }
  };

  const randomTime = () => Math.floor(Math.random() * 60);

  const makeProgress = () => {
    if (isLoading) return;
    if (!chooseFileBtn.value) {
      alert("Choose a file");
    } else {
      isActive = true;
      progress = setInterval(addColor, randomTime());
    }
  };

  setTimeout(() => {
    isLoading = false;
  }, 10);
</script>


<style>
  #progress {
    width: 100%;
    height: 50px;
    position: relative;
    margin-top: 5%;
    display: flex;
    align-items: center;
  }

  #myBar {
    height: 40px;
    background-color: #9ee86a;
    border: 1px solid #333;
    transition: width 0.3s;
    position: relative;
    overflow: hidden;
  }

  #myBar span {
    font-size: 1.2rem;
    color: white;
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    z-index: 1;
  }

  form {
    margin-top: 1%;
    width: 100%;
    text-align: center;
  }

  button {
    cursor: pointer;
    width: 100px;
    padding: 9.5px 0;
  }

  button:active {
    background-color: hsl(20, 58%, 55%);
    color: white;
  }

  @keyframes blink {
    to {
      opacity: 0;
    }
  }

  #loading-text {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
  }

  #msg-cont {
    height: 50px;
    padding: 10px;
  }

  h3#completion-msg {
    margin: 0 auto;
    text-align: center;
    animation: blink 1.5s ease-in-out 4;
  }
</style>




{#if isLoading}
  <!-- Show loading text while isLoading is true -->
  <div id="loading-text">Loading...</div>
{:else}
  <!-- Show the application content when isLoading is false -->
  <div>
    <div id="msg-cont">
      {#if showCompletionMsg}
        <h3 id="completion-msg" in:fly={{ x: -300 }} out:fly={{ x: 300 }}>
          The file has been uploaded! The progress indicator works!
        </h3>
      {/if}
    </div>

    <div id="progress">
      {#if isActive}
        <div id="myBar" style="width: {barWidth}%">
          <span>{barWidth}%</span>
        </div>
      {/if}
    </div>

    <form on:submit|preventDefault={makeProgress}>
      <input type="file" id="myFile" name="filename" bind:this={chooseFileBtn}>
      <button disabled={isActive}>Upload File</button>
      <button on:click|stopPropagation|preventDefault={resetBar}>Cancel</button>
    </form>
  </div>
{/if}


Svelte application
Copyright ©2024 Educative, Inc. All rights reserved