I’ve done this in dash-bootstrap-components docs, see relevant CSS here. The basic idea is to hide the existing spinner by targeting the _dash-loading
CSS class, then attach your custom spinner with ::after
.
In the above example the spinner-border
argument to animation
is a webkit animation defined by Bootstrap so that exact code won’t work out the box if you’re not using Bootstrap, but the same principle can be applied to other CSS spinners. For example I took one of the spinners from here and fiddled around a bit to get the following example.
import time
import dash
import dash_html_components as html
def slow_serve_layout():
time.sleep(5)
return html.Div("Hello, world!")
app = dash.Dash()
app.layout = slow_serve_layout
if __name__ == "__main__":
app.run_server(debug=True)
._dash-loading {
margin: auto;
color: transparent;
width: 2rem;
height: 2rem;
text-align: center;
}
._dash-loading::after {
font-size: 60px;
content: "";
display: inline-block;
width: 1em;
height: 1em;
color: #000;
border-radius: 50%;
margin: 72px auto;
vertical-align: text-bottom;
-webkit-animation: load6 1.7s infinite ease, round 1.7s infinite ease;
animation: load6 1.7s infinite ease, round 1.7s infinite ease;
margin-top: 2rem;
}
@-webkit-keyframes load6 {
0% {
box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em,
0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
}
5%,
95% {
box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em,
0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
}
10%,
59% {
box-shadow: 0 -0.83em 0 -0.4em, -0.087em -0.825em 0 -0.42em,
-0.173em -0.812em 0 -0.44em, -0.256em -0.789em 0 -0.46em,
-0.297em -0.775em 0 -0.477em;
}
20% {
box-shadow: 0 -0.83em 0 -0.4em, -0.338em -0.758em 0 -0.42em,
-0.555em -0.617em 0 -0.44em, -0.671em -0.488em 0 -0.46em,
-0.749em -0.34em 0 -0.477em;
}
38% {
box-shadow: 0 -0.83em 0 -0.4em, -0.377em -0.74em 0 -0.42em,
-0.645em -0.522em 0 -0.44em, -0.775em -0.297em 0 -0.46em,
-0.82em -0.09em 0 -0.477em;
}
100% {
box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em,
0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
}
}
@keyframes load6 {
0% {
box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em,
0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
}
5%,
95% {
box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em,
0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
}
10%,
59% {
box-shadow: 0 -0.83em 0 -0.4em, -0.087em -0.825em 0 -0.42em,
-0.173em -0.812em 0 -0.44em, -0.256em -0.789em 0 -0.46em,
-0.297em -0.775em 0 -0.477em;
}
20% {
box-shadow: 0 -0.83em 0 -0.4em, -0.338em -0.758em 0 -0.42em,
-0.555em -0.617em 0 -0.44em, -0.671em -0.488em 0 -0.46em,
-0.749em -0.34em 0 -0.477em;
}
38% {
box-shadow: 0 -0.83em 0 -0.4em, -0.377em -0.74em 0 -0.42em,
-0.645em -0.522em 0 -0.44em, -0.775em -0.297em 0 -0.46em,
-0.82em -0.09em 0 -0.477em;
}
100% {
box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em,
0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
}
}
@-webkit-keyframes round {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes round {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}