To Infinity And loading.......
Jake Goldsborough (@rjgoldsborough) & Will Hutchinson (@tetowill)
august 4th, 2015
Jake: 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.
Tags: technology css animation