Smooth page transitions when loading data

I can’t find it now but someone posted on twitter about trying to avoid showing loading spinners for very short amounts of time on page loads and it has come up in some work I’m doing at the moment.

I have a flow where you click on a link to a new page in a single-page-app, the url changes, the routing is handled and tries to load the data for that page over http using RemoteData, and the page shows a loading spinner for a very short amount of time and then the page content when the http response has come back.

What is the best way to avoid it? The post I was reading talked in terms of waiting a little on the current page to see if the data comes back quickly and the transitioning completely to the final content. Only showing spinner after a set amount of time. And goes further to say that you could consider leaving the loading spinner in place for longer than needed if the response comes back just after you have started to display the spinner to avoid having it flash up briefly.

I’ve attempted this before in Javascript and ended up doing some kind of Promise.race or maybe something with an RxJS type library. What is the best approach in Elm? There doesn’t seem to be a Task.race. Maybe something with the Process module? I’m not experienced with it though.

yep, Process.sleep will let you do this. As a sketch:

type alias Model = { pageData : WebData Something, spinning : Bool }

type Msg
    = LoadPage
    | GotData (WebData Something)
    | WaitedLongEnough

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of
        LoadPage ->
            ( model
            , Cmd.batch
                [ loadData
                , Process.sleep (10 * Time.millisecond)
                    |> Task.perform (\_ -> WaitedLongEnough)
                ]
            )

        GotData stuff ->
            ( { model | pageData = stuff, spinning = False }, Cmd.none )

         WaitedLongEnough ->
             ( { model | spinning = isStillLoading model.pageData }, Cmd.none )

This might need some massaging in the batch call—I don’t think it’ll block but I haven’t tried it.

1 Like

This is indeed the general pattern one wants to apply though with the caveat that you are dealing with async logic here and hence if you can have multiple requests in flight you need a way to know how to make sure that the response you are getting is the one you are interested in. For example, imagine an old timer firing and causing a load that has only been underway for a short period of time to be identified as a long load. I tend to handle this by keeping a counter in the model and having the timer I’m interested in return with the counter value at the time I created it. If the timer message value still matches the current counter index, it’s the timer I was waiting for.

Mark

got a simple version of this working here: https://ellie-app.com/cFB2QWYPXa1/0

2 Likes

Sometimes, things like this can be done with CSS.

.spinner {
  animation: spinner 200ms 300ms forwards;
}

@keyframes spinner {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

Render your spinner right away, but it won’t show for 300ms. Then, it’ll fade in for 200ms, and then stay visible until removed.

5 Likes

It may not apply to your scenario, but another option is to skip the loading indicator and instead show a shell version of that page that is filled in with data as it is available.

Something like this https://codepen.io/andiawesome/pen/akgQBR

1 Like