Building a pure CSS loading spinner

... and user friendly implementation ...

(image src not coded yet)

Intro

Often pure CSS spinners need quite a lot of div's or other HTML elements to accomplish the figure. The snake-like CSS spinners I found have mostly a tail not exceeding the half of a circle. My challenge was to build a pure CSS spinner with only one HTML element, with a tail to 3/4 of the circle, and with support by Internet Explorer 11.

The inspiration to make this spinner was generated by Ana Tudor (thebabydino) 's article "Creating Yin and Yang Loaders On the Web" at CSS-tricks.com. When the spinner was ready, I became curious how it would behave in a real web page. That was quite an other story.

Francky Kleyneman
dec. 2018

  1. Topography and Construction
  2. Variations
  3. Reality check
  4. CSS only improvements
  5. CSS & javascript
  6. Conclusions
  7. Recommendations (and strange browser differences)

Topography

The snake is circling in an orbit of a constant width (in the image the light green orbit), so the imaginary backbone of the snake has to end just in the middle of the snake orbit at the bottom.

Construction

Step 1

In the HTML we have a <div>, in which the spinner and the loading element are positioned together. The loading element can be an image, or maybe some other element. In this story an image is used, so we set the surrounding box as <div class="imgBox">
The spinner uses a box-sizing: border-box; in order to get everything in the right position. Then the spinner has the background-color of the page (or background-color of the imgBox, if the imgBox has a different background-color). - In the illustrations of the construction steps over here:

The temptation is great to start with filling the circle with the background-color of the page/imgBox, and then to put several snake elements on top of it. My experience: that will result in an extra HTML element inside the spinner !
The trick is just the opposite: we start with filling the complete circle in the green snake color, as background-color. Afterwards we can put the not-snake parts in the circle, in the background-color of the page/imgBox.
So we have this:

<div class="imgBox">
  <div class="spinner"></div>
  <img src="images/..." alt="...">
</div>
<script> /* none */ <script>
.imgBox {
/* ... custom properties ... */
background: #77B7B; /* tmp > #EFEFEF */
position: relative;
}
.spinner {
position: absolute;
box-sizing: border-box;
top: 20px;
left: 50%;
margin-left: -25px;
width: 50px;
height: 50px;
background: green;
border-radius: 50%;
}
show circle

Step 2

It happens that there is no need to consume a first :before selector for the "transparent" outer part of the snake. You can arrange this by sculpturing the borders of the spinner itself.
Then in the center of the green ink stain, and in the bottom right quadrant we need to cut some holes (= an overlay with the page/imgBox background-color). But that's for later.

<div class="imgBox">
  <div class="spinner"></div>
  <img src="images/..." alt="...">
</div>
<script> /* none */ <script>
.imgBox {
/* nothing changed */
}

.spinner {
/* ... styles from above ... */
border-style: solid;
border-color: #EFEFEF;
border-width: 2px 0 6px 4px;
}
show step show circle

Step 3

The counterfeited center hole is taking place in the :before selector, with a different radius for each of the 4 borders to get the desired spiral shape. The bottom right corner can stay rectangular.

<div class="imgBox">
  <div class="spinner"></div>
  <img src="images/..." alt="...">
</div>
<script> /* none */ <script>
.spinner:before {
position: absolute;
content: "";
top: 10px;
left: 4px;
width: 30px;
height: 33px;
border-top-left-radius: 110px;
border-top-right-radius: 100px;
border-bottom-left-radius: 130px;
border-bottom-right-radius: 0;
background: fuchsia; /* tmp > #EFEFEF */
}
show step show circle

Step 4

Painting the bottom right quadrant with a solid background-color of the page/imageBox and then paste a green circle on top for the head of the snake should ask for two hooks in the code, and we have only one :after selector left ...
We need, again, the inverse.
The green is already there, so the head circle can be transparent, and the rest could be painted in the background color in order to hide the unnecessary green. But a negative border radius doesn't exist.
The rescue is to use a radial gradient background-image to get a transparent circle part (see: Lea Verou 's article Beveled corners & negative border-radius with CSS3 gradients).

<div class="imgBox">
  <div class="spinner"></div>
  <img src="images/..." alt="...">
</div>
<script> /* none */ <script>
.spinner:after {
position: absolute;
content: "";
top: 24px;
right: 0;
width: 25px;
height: 18px;
border-bottom-right-radius: 100px;
background-image:
  radial-gradient(circle at 19px 0,
    transparent 6px, fuchsia 6px);
  /* tmp > #EFEFEF */
}
show step show circle

Step 5

The eyes of the snake (or is it a fish or a tadpole?) are a bit over the bottom half of the image (see Topography above), so we can't use the :after for it: that can not exceed the bottom half. Also the :before is not suitable.
But the surface of the spinner itself has space enough, so the spinner CSS can get two radial gradient background-images (white eyes, the rest transparent!).

<div class="imgBox">
  <div class="spinner"></div>
  <img src="images/..." alt="...">
</div>
<script> /* none */ <script>
.spinner {
/* ... as above ... */
background-image:
  radial-gradient(circle at 37px 24px,
    white 1.5px, transparent 1.5px),
  radial-gradient(circle at 43px 25px,
    white 1.5px, transparent 1.5px);
background-repeat: no-repeat;
}
show circle

Step 6

Removing the background-color of the imgBox and adding the rotation: ready!

<div class="imgBox">
  <div class="spinner"></div>
  <img src="images/..." alt="...">
</div>
<script> /* none */ <script>
.imgBox {
/* ... custom properties ... */
background: #EFEFEF;
position: relative;}
.spinner {
/* ... as above ... */
animation: 1.5s spinning linear
  infinite;
}
@keyframes spinning {
to {transform: rotate(360deg); }
}
show circle