Learn Docker With My Newest Course

Dive into Docker takes you from "What is Docker?" to confidently applying Docker to your own projects. It's packed with best practices and examples. Start Learning Docker →

Customizing Topbar to Add a Delay Before Showing Page Loading Progress

blog/cards/customizing-topbar-to-add-a-delay-before-showing-page-loading-progress.jpg

We're going to look at a Phoenix LiveView app for this example but this applies to any web app using any web framework.

Quick Jump: Delaying Topbar Will Make Your Pages Feel Faster | Demo Video

It’s a nice UI enhancement to add a little progress bar at the top of a page when it’s loading. Lots of sites do this like GitHub, YouTube and more. I’m sure you’ve noticed a little bar that loads at the top of these sites as you transition between pages.

When you build a site using pjax, Turbolinks, Hotwire Turbo, htmx, LiveView or a SPA (single page app) you can get the benefit of being able to do very fast page transitions where at the very least you only swap the <body></body> of the page instead of doing a full reload.

Depending on which tech you use, you can also have more fine grained control such as only partially updating a tiny area of the page that needs to change but that’s not an important detail for now.

Personally every site I’ve built since 2016 or so has used one of the above technologies. Mainly Turbolinks and now Hotwire Turbo which is its successor. An example of one is my Running in Production podcast site which uses Turbolinks.

In any case, if you’re using Phoenix, out of the box it will use a JS library called Topbar to control rendering this progress bar. nProgress is another option that used to be very popular. Both of these libraries work with any web framework.

Delaying Topbar Will Make Your Pages Feel Faster

The video below is going to show how you can add an initial delay to Topbar so it only shows up for responses that take longer than 500ms. This is what Hotwire Turbo does by default but Topbar doesn’t have this feature (at least not at the time of making this video).

Even if Phoenix LiveView decides to use something else in the future this same concept applies. You can use exactly the same strategy with something other than Topbar.

In my opinion there is a huge win from doing this because when you show this progress bar on fast loading pages then it creates an illusion that the page is loading slower than it is really is due to the progress bar animation taking longer to finish than it takes for the page to load. We’ll see a before / after of this on video.

Demo Video

Code snippets

After getting the docker-phoenix-example app running you’ll want to make the following changes, or if you already have a Phoenix app feel free to add these changes in or use your existing live views.

Add this to assets/js/app.js (this is already in the repo, but it’s here for a reference):

import topbar from "topbar"

let topBarScheduled = undefined

topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"})

window.addEventListener("phx:page-loading-start", (info) => {
  if(!topBarScheduled) {
    topBarScheduled = setTimeout(() => topbar.show(), 500)
  }
})
window.addEventListener("phx:page-loading-stop", (info) => {
  clearTimeout(topBarScheduled)
  topBarScheduled = undefined
  topbar.hide()
})

Add this to lib/hello_web/router.ex:

live "/1", DemoLive.One, :index
live "/2", DemoLive.Two, :index

Create lib/hello_web/live/demo/one.ex:

defmodule HelloWeb.DemoLive.One do
  use HelloWeb, :live_view

  @impl true
  def mount(_params, _session, socket) do
    {:ok, socket}
  end

  def render(assigns) do
    ~H"""
    <div class="pt-12">
      <%= live_patch "Page Two", to: Routes.demo_two_path(@socket, :index) %>
      <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 inline-block" fill="none" viewBox="0 0 24 24" stroke="currentColor">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6" />
      </svg>
    </div>
    """
  end

  @impl true
  def handle_params(params, _url, socket) do
    {:noreply, apply_action(socket, socket.assigns.live_action, params)}
  end

  defp apply_action(socket, :index, _params) do
    socket
    |> assign(:page_title, "One")
  end
end

Create lib/hello_web/live/demo/two.ex:

defmodule HelloWeb.DemoLive.Two do
  use HelloWeb, :live_view

  @impl true
  def mount(_params, _session, socket) do
    {:ok, socket}
  end

  def render(assigns) do
    ~H"""
    <div class="pt-12">
      <%= live_patch "Page One", to: Routes.demo_one_path(@socket, :index) %>
      <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 inline-block" fill="none" viewBox="0 0 24 24" stroke="currentColor">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6" />
      </svg>
    </div>
    """
  end

  @impl true
  def handle_params(params, _url, socket) do
    {:noreply, apply_action(socket, socket.assigns.live_action, params)}
  end

  defp apply_action(socket, :index, _params) do
    socket
    |> assign(:page_title, "Two")
  end
end

Create lib/hello_web/live/live_helpers.ex:

defmodule HelloWeb.LiveHelpers do
  import Phoenix.LiveView
  import Phoenix.LiveView.Helpers
end

Timestamps

  • 0:10 – What is a site-wide progress indicator or topbar?
  • 0:32 – This works with pjax, Turbolinks, Hotwire Turbo, htmx, LiveView or SPAs
  • 1:03 – Not showing the topbar for fast responses ends up being a huge win
  • 1:42 – Hotwire Turbo has a built in progress bar delay set to 500ms by default
  • 2:49 – I brought this up with the creator of Elixir and he added it to the docs
  • 3:43 – Going over the issue by looking at the before version of the app
  • 6:13 – Implementing a bit of code to delay showing topbar and checking out the results
  • 10:29 – Adding an artificial delay to make sure the progress bar still shows
  • 11:40 – This delayed progress bar is nice to avoid seeing it during network blips too
  • 12:24 – Wrapping things up, any questions?

Which progress bar solution do you use? Let me know below.

Never Miss a Tip, Trick or Tutorial

Like you, I'm super protective of my inbox, so don't worry about getting spammed. You can expect a few emails per month (at most), and you can 1-click unsubscribe at any time. See what else you'll get too.



Comments