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>
</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";
}
}