This applies for people creating/updating extensions for versions after beta 16 (not including beta 16), such as stable.


Flarum core's loading indicator has changed a lot since beta 16, so here's the low-down on how to upgrade your code.

Instead of using a Javascript-based loading spinner library, we use a pure CSS spinner. CSS is almost always more performant than Javascript, and it also reduced our bundle size by a small touch. Every little helps!

As always, using the Mithril component (<LoadingIndicator />) is the recommended way to use the spinner.

Preface: ye olde spinner

Our old spinner used spin.js, meaning a new spinner object was created every time a spinner needed to be used. This could be rendered, resulting in a div containing another spinner div, with multiple divs inside that, which each have a div, which then each get animated with Javascript. It gets pretty messy. Just look at the markup!

<div class="LoadingIndicator LoadingIndicator--block">
  <div class="spinner" role="progressbar" style="position: absolute; width: 0px; z-index: auto; left: 50%; top: 50%; transform: scale(1);">
    <div style="position: absolute; top: -1.5px; width: 7px; height: 3px; background: transparent; border-radius: 1.5px; transform-origin: left center; transform: rotate(0deg) translateX(5px);">
      <div style="width: 100%; height: 100%; background: rgb(108, 126, 147); border-radius: 1.5px; opacity: 0.33943;"></div>
    </div>
    <div style="position: absolute; top: -1.5px; width: 7px; height: 3px; background: transparent; border-radius: 1.5px; transform-origin: left center; transform: rotate(45deg) translateX(5px);">
      <div style="width: 100%; height: 100%; background: rgb(108, 126, 147); border-radius: 1.5px; opacity: 0.43318;"></div>
    </div>
    <div style="position: absolute; top: -1.5px; width: 7px; height: 3px; background: transparent; border-radius: 1.5px; transform-origin: left center; transform: rotate(90deg) translateX(5px);">
      <div style="width: 100%; height: 100%; background: rgb(108, 126, 147); border-radius: 1.5px; opacity: 0.52693;"></div>
    </div>
    <div style="position: absolute; top: -1.5px; width: 7px; height: 3px; background: transparent; border-radius: 1.5px; transform-origin: left center; transform: rotate(135deg) translateX(5px);">
      <div style="width: 100%; height: 100%; background: rgb(108, 126, 147); border-radius: 1.5px; opacity: 0.62068;"></div>
    </div>
    <div style="position: absolute; top: -1.5px; width: 7px; height: 3px; background: transparent; border-radius: 1.5px; transform-origin: left center; transform: rotate(180deg) translateX(5px);">
      <div style="width: 100%; height: 100%; background: rgb(108, 126, 147); border-radius: 1.5px; opacity: 0.71443;"></div>
    </div>
    <div style="position: absolute; top: -1.5px; width: 7px; height: 3px; background: transparent; border-radius: 1.5px; transform-origin: left center; transform: rotate(225deg) translateX(5px);">
      <div style="width: 100%; height: 100%; background: rgb(108, 126, 147); border-radius: 1.5px; opacity: 0.80818;"></div>
    </div>
    <div style="position: absolute; top: -1.5px; width: 7px; height: 3px; background: transparent; border-radius: 1.5px; transform-origin: left center; transform: rotate(270deg) translateX(5px);">
      <div style="width: 100%; height: 100%; background: rgb(108, 126, 147); border-radius: 1.5px; opacity: 0.90193;"></div>
    </div>
    <div style="position: absolute; top: -1.5px; width: 7px; height: 3px; background: transparent; border-radius: 1.5px; transform-origin: left center; transform: rotate(315deg) translateX(5px);">
      <div style="width: 100%; height: 100%; background: rgb(108, 126, 147); border-radius: 1.5px; opacity: 0.99568;"></div>
    </div>
  </div>
  &nbsp;
</div>

New spinner

Instead, now we just have two elements: a container and the spinner itself.

<!-- Don't write this markup manually, use the core component instead -->
<div class="LoadingIndicator--container">
  <div class="LoadingIndicator"></div>
</div>

How it works

The spinner container handles all of the spacing and size requirements required by the design system. If you need the spinner to have 16px of space above and below, this would be set on the container, not the spinner itself.

The spinner is a div of fixed size, positioned in the middle of the container. The spinner has a border on all sides, with one side set to transparent, creating the 3/4 effect.

We then use CSS keyframe animations to spin the element. Neat!

Differences

Spacing (padding, custom height)

The container should receive any positioning or spacing requirements your use case needs.

An example is the display attribute that can be passed to our Mithril component. This can be set to "inline" or "block". Setting this to "block" applies a style of height: 100px to the container, then the spinner itself is centered inside the container in both directions.

If you set a height or padding on the spinner itself, it will become deformed.

Custom spinner sizes and thickness

Our Mithril component provides 3 size options: small, medium, and large. These should be fine for most cases. tiny is no longer a valid size as has been replaced with small.

<LoadingIndicator size="small" />

If you have a more complex use case and need a custom size, you'll need to use CSS custom properties to set these values. Please note that these properties are set on the container and not the spinner.

.LoadingIndicator-container {
  // width and height
  --size: 48px;
  // stroke size
  --thickness: 4px;
}

Inline and block options

Before, you would add LoadingIndicator--block or LoadingIndicator--inline to the spinner itself to apply the respective styles. Now you need to apply these classes to the container. They have also been renamed to LoadingIndicator-container--block and LoadingIndicator-container--inline, respectively.

If you're using our Mithril component, you can supply either block or inline as attributes, like this:

<LoadingIndicator display="block" />

Our component defaults to adding the block class. You can remove this by providing display="unset" to the component.

Setting custom classes

You'll likely need to set custom classes when using the new loading indicator. This can be done quickly using our Mithril component:

<LoadingIndicator containerClassName="mySpinnerContainer" className="mySpinner" />

As above, spacing should be performed on the container element and not the spinner itself.

Custom theming

Custom theming has been sacrificed as part of this transition. It's still completely possible to do, but a little trickier. Please note that removing the spinner's default looks may hurt my feelings.

If you want to use something like a Font Awesome icon for the spinner, you'll need to remove the border-radius and border, then set the font-family and content properties.

// This isn't complete and would likely need tweaking
.LoadingIndicator {
  border-radius: 0;
  border: none;
  font-family: 'Font Awesome 5 Free';
  -webkit-font-smoothing: antialiased;
  display: inline-block;
  font-style: normal;
  font-variant: normal;
  text-rendering: auto;
  line-height: 1;

  &::before {
    content: "\f1ce";
  }
}
    a month later

    davwheat This means the old spinner was somehow also eating the speed of the site. I need assistance to remove the old code and use only css spinner

    inside /vendor/flarum/core/js/src/common/components/ i have LoadingIndicator.js
    is there a code to be changed in the js file?

      Braden

      The change is included in the 1.0 update.

      We don't support updating individual parts of core with features from later versions, so you should instead fully update to v1.0.2.

      Instructions are available in a stickied post on the forum: https://discuss.flarum.org/d/27394-flarum-100-released/1

      The old spinner wasn't slowing the forum, but it could be improved. Every small improvement counts towards the big picture!

      davwheat changed the title to [1.0+] Using the new loading spinner .

      On my forum, the new spinner (oval thing) only shows on index page, the rest of pages is still round thing. Sopose to be like that?

      I stayed whole night trying to fix this thing in flarum15, it seem to be a small thing Loadingindicator but was unsuccessful. I am ashamed - going back to sleep

        15 days later

        davwheat thanks ahah I initially thought that LoadingSpinner was wrapping LoadingIndicator or something like that. Mystery solved.

        6 days later

        Love me the new spinner!

        I hit one issue with it with fof/upload though, I am not sure if it should be reported there, here, or maybe even rich text extension. When uploading it looks like the below.

          ctml does it require both Rich Text and FoF Upload to reproduce?

          If it reproduces just with Upload, then it should be reported to that extension.

          Most likely it will have something to do with some custom CSS or another enabled extension because I doubt this would have passed our QA tests in FriendsOfFlarum without anyone noticing.

            clarkwinkelmann I disabled rich text and it still occurred so that rules that out. I don't have any css for .LoadingIndictator-container--block which is looks like where it gets its enormous height.

            this happens to me too, its fof / Upload css issue? button get bigger after selecting the file from local . but it will work okay if we post from our clipboard.

            Happens with Imgur only?

            matteocontrini i don't know how to report with technical details, could you create an issue at GitHub.