Blazor Client - Loading Screen

15/07/2022
BlazorUXC#

If you are using Blazor WebAssembly aka client-side Blazor you are faced with an issue: The .NET runtime including your assemblies has to be downloaded first. We are taking about some megabytes as the initial load.

Depending on the connection of your client there is a time when basically nothing happens. The default template just has a simple "Loading..." text. So let's change that.

First try: Make it nice

All the magic happens in the index.html and its stylesheet of your project

BlazorWASMProject/
+- wwwroot/
¦  +- app.css
¦  +- index.html

The important part inside the index.html is this:

<body>
    <!-- Everything under app will be shown before our content is loaded -->
    <!-- Once the application is loaded, it will remove that part-->
    <div id="app">
        <p>Loading...</p>
    </div>

Everything under app will be shown until the application is downloaded and started. So whatever we do here we can show as this will be initially loaded. A word of warning: Don't put super fancy and "expensive" (in terms of payload) resources here as it further delays the loading.

You can basically do whatever you want, I took this example on codepen as a template:

<div id="app">
  <div class="position-relative mt-5">
    <div class="position-absolute top-50 start-50 translate-middle w-25 text-center">
      <div class="loading">
        <div class="bounceball"></div>
        <div class="text">Loading</div>
      </div>
    </div>
  </div>
</div>

Plus I added the following part to the app.css:

.text {
    color: #fbae17;
    display: inline-block;
    margin-left: 5px;
}

.bounceball {
    position: relative;
    display: inline-block;
    height: 37px;
    width: 15px;
}
.bounceball:before {
    position: absolute;
    content: "";
    display: block;
    top: 0;
    width: 15px;
    height: 15px;
    border-radius: 50%;
    background-color: #fbae17;
    transform-origin: 50%;
    -webkit-animation: bounce 500ms alternate infinite ease;
    animation: bounce 500ms alternate infinite ease;
}    

@keyframes bounce {
    0% {
        top: 30px;
        height: 5px;
        border-radius: 60px 60px 20px 20px;
        transform: scaleX(2);
    }
    35% {
        height: 15px;
        border-radius: 50%;
        transform: scaleX(1);
    }
    100% {
        top: 0;
    }
}

A nice mix of Boostrap 5 and a bit of animation in CSS. The result will look like this:

First Try

That is way more pleasing than the "Loading..." text. But still we have a problem from a UX point of view. We don't give any indication if there is progress at all for the user. So to do that, we have to tweak some stuff. Let's do it (promised it is quite easy).

Second try: Loading bar

Blazor gives us the possibility to hook into its lifecycle. And that is exactly what we gonna do. First we will create a new placeholder for a loading bar directly under our bouncing ball animation:

<div class="text">Loading</div>
<!--                  Make a progress bar so the user sees if something is happening-->
        <div class="progress mt-2" style="height: 2em;">
          <div id="progressbar" class="progress-bar progress-bar-striped progress-bar-animated"
               style="width: 0; background-color: #204066;"></div>
        </div>

Via default Blazor automatically loads all the stuff from the server, but we can and have to disable that behavior and do parts on our own. So locate this line:

<script src="_framework/blazor.webassembly.js"></script>

And change it to:

<script src="_framework/blazor.webassembly.js" autostart="false"></script>

As you can see we added autostart="false" as attribute. Now we are responsible to bootstrap the process. That is annoying but gives us the option to know where we are in the progress:

<script>
  function StartBlazor() {
    let loadedCount = 0;
    let allRessources = 0;
    Blazor.start({
      // This function gets called by the Blazor pipeline
      loadBootResource:
        function (type, filename, defaultUri, integrity) {
          if (type === "dotnetjs")
            return defaultUri;

          // As "fetchResources" is a promise we don't block
          const fetchResources = fetch(defaultUri, {
            cache: 'no-cache',
            integrity: integrity
          });
          
          // Add one to the total amount of stuff we have to fetch
          allRessources++;

          // That promise is fulfilled once one resource is done fetching
          // When this happens we update the progress bar
          fetchResources.then((_) => {
            // When fetching is done, indicate this in our loading bar
            loadedCount++;
            const percentLoaded = 100 * loadedCount / allRessources;
            const progressbar = document.getElementById('progressbar');
            progressbar.style.width = percentLoaded + '%';
          });

          return fetchResources;
        }
    });
  }

  // Invoke the pipeline
  StartBlazor();
</script>

And that is it. We can now review our results:

Second Try

Conclusion

Well, that is way more appealing and also gives the user some sense of progress! And that wasn't hard, was it? With that little implementation, you helped out your users a lot. You can even extend this to show which file is currently downloading.

Resources:

  • This example can be found here.
  • All my blog post examples can be found here.
5
An error has occurred. This application may no longer respond until reloaded. Reload x