To Infinity And loading.......

« Return to Our Notebook

To Infinity And loading.......

Recently, I was working on an internal project and started thinking about the infinity symbol. After reading Will's great post on recreating the Archer title sequence with CSS animations, I came up with the idea to create a loader using the symbol. A loader is an animation used to signal to the user that something is happening, like data loading or when submitting a form.

A Loader Needs an Animation

My idea for the animation was to animate a small ball to follow the inside of the infinity symbol. I found this great CSS infinity symbol on CSS Tricks, courtesy of Nicolas Gallagher. Initially I tried to accomplish this using CSS by updating the positioning of the ball, but I quickly found that this was very tedious and not that smooth. I decided it was time to reach out to Will and see if he had any advice.

Let’s Get the Ball Rolling

When Jake came to me with the infinity loader idea I was excited to jump in and give him a hand. Looking at the shape of the infinity symbol, I focused on the fact that it is essentially two circles with a small cross section in the middle that straightens out to connect them. Looking at it this way, it makes sense to move the ball using transform: rotate() and transform-origin.

As a test I set up the ball to make the left circle. The ball is 20px to match the width of the symbol's border. I'm using position: absolute with the top set at 50% and left at 0. There's also a margin-top of -10px, which is half of the ball's height, to center the ball. The transform: rotate() is set to start at 0deg. The transform-origin has an x-offset of 50px, which matches the infinity symbol's border-radius, and a y-offset of 10px, which is half of the ball's height. These offsets position the ball's rotation point on the center of the left circle.

.ball {
  height: 20px;
  width: 20px;
  border-radius: 50%;
  background: #fff;
  position: absolute;
  top: 50%;
  left: 0;
  margin-top: -10px;
  z-index: 10;
  transform: rotate(0deg);
  transform-origin: 50px 10px;
  animation: infinity 2s linear infinite;
}

@keyframes infinity {
  to {
    transform: rotate(360deg);
  }
}

Adjusting transform: rotate() and transform-origin to move our ball works well and is going to simplify our movement significantly compared to manipulating the actual positioning. We will still need to modify the positioning to move the ball to the right side. Let’s take a look at that now and make the right circle as well.

Slide to the Right

For moving the ball over to the right side, we'll switch from using left to right positioning. This makes the most sense, since the position would stay the same regardless of the width of the infinity symbol. To do this we set the left position to auto and the right position to 0. We’ll also need to set the transform-origin x-offset to -30px so that the rotation is positioned properly for the right side of the symbol. To get -30px we take the original 50px offset we used on the left side and subtract the width of the ball, since we're moving the offset negatively for the right side.

@keyframes infinity {
  0% {
    transform: rotate(0deg);
    transform-origin: 50px 10px;
    left: 0;
  }
  50% {
    transform: rotate(360deg);
    transform-origin: 50px 10px;
    left: 0;
  }
  /* switch sides */
  50.1% {
    left: auto;
    right: 0;
    transform: rotate(0deg);
    transform-origin: -30px 10px;
  }
  100% {
    left: auto;
    right: 0;
    transform: rotate(360deg);
    transform-origin: -30px 10px;
  }
}

After a bit of browser testing though, Jake noticed that something wasn’t working properly in Firefox and Internet Explorer. Turns out those browsers don't switch the positioning from left to right during the animation. Instead, they keep the left positioning and the ball makes the second circle further over to the left. To fix this, we'll have to stick with using left positioning. Not a big deal.

So now we have our ball making both the left and right circles properly in all browsers.

@keyframes infinity {
  0% {
    left: 0;
    transform: rotate(0deg);
    transform-origin: 50px 10px;
  }
  50% {
    left: 0;
    transform: rotate(360deg);
    transform-origin: 50px 10px;
  }
  50.1% {
    left: 192px;
    transform: rotate(0deg);
    transform-origin: -30px 10px;
  }
  100% {
    left: 192px;
    transform: rotate(360deg);
    transform-origin: -30px 10px;
  }
}

Making the Connection

Things are coming along nicely now, so let's join the two circles. We want to start our ball in the center, so let’s first flip our starting rotation to 180deg.

Now we need to nudge it over to the right a bit. To do that we are going to stick with the other property we’re animating and set our transform-origin x-offset to 58px. To get 58px I nudged the x-offset until the ball looked centered. Here's a more mathematical approach if you'd prefer. Our infinity symbol halves have a diameter of 100px and the total width including the cross section is 212px. If we subtract the two circles from our total width we're left with 12px. Our ball is 20px, and 20px minus 12px leaves us with an additional 8px to center the ball. Calculating the offset this way works for any size symbol as long as we keep the current ratios.

.ball {
  height: 20px;
  width: 20px;
  border-radius: 50%;
  background: #fff;
  position: absolute;
  top: 50%;
  left: 0;
  margin-top: -10px;
  z-index: 10;
  transform: rotate(180deg);
  transform-origin: 58px 10px;
}

Now that we’re all nice and centered, let’s set up our animation again. To center the ball for the start of the right circle, let’s flip the rotation to -180deg and nudge the transform-origin x-offset to -38px. Then we’ll modify both circles’ ending values to match up with the new starting positions. That’ll get the ball moving continuously through the center of our symbol.

@keyframes infinity {
  0% {
    left: 0;
    transform: rotate(180deg);
    transform-origin: 58px 10px;
  }
  50% {
    left: 0;
    transform: rotate(-180deg);
    transform-origin: 58px 10px;
  }
  50.1% {
    left: 192px;
    transform: rotate(-180deg);
    transform-origin: -38px 10px;
  }
  100% {
    left: 192px;
    transform: rotate(180deg);
    transform-origin: -38px 10px;
  }
}

Staying on Track

With the two circles connected, things are looking pretty smooth, but not really following that infinity symbol all too well. A few tweaks and this ball will be on track.

When we started this animation we had our left rotation with a transform-origin x-offset of 50px and our right rotation with a transform-origin x-offset of -30px. So to get things where they need to be, we’ll animate our transform-origin between our current and original values when the ball is crossing that center section. To get a starting point for the percentage of time needed for this transition we can break the animation down into segments. We have two halves, each with four corners and each corner has two segments. This gives us 16 segments in total, four are part of our cross section. 100% divided by 16 gives us 6.25%, so I started out with 6% per cross segment for this transition. After fiddling around with the timing of these transitions a bit (I decided that 4% looked best), we have our lovely loader animation working just like we'd envisioned.

@keyframes infinityFinal {
  0% {
    transform: rotate(180deg);
    transform-origin: 58px 10px;
    left: 0;
  }
  4% {
    transform-origin: 50px 10px;
  }
  46% {
    transform-origin: 50px 10px;
  }
  50% {
    transform: rotate(-180deg);
    transform-origin: 58px 10px;
    left: 0;
  }
  50.1% {
    left: 192px;
    transform: rotate(-180deg);
    transform-origin: -38px 10px;
  }
  54% {
    transform-origin: -30px 10px;
  }
  96% {
    transform-origin: -30px 10px;
  }
  100% {
    left: 192px;
    transform: rotate(180deg);
    transform-origin: -38px 10px;
  }
}

The End

Wait... it’s an infinity symbol, there is no end. Anyway, we hope you enjoyed the post, and if you’d like to play around with this on your own you can view the final version on CodePen.

We solve problems with technology. What can we solve for you?

Reach Out

t: 800.646.0188