See the Pen Article: Circle illusion with Tusi Motion #1 by Jaime Caballero (@jaicab) on CodePen.
I made this illusion based on the Tusi motion method, which takes his name from Nair al-Din Tusi, a Persian astronomer from the 13th century. The principle established by Tusi states that:
If a circle with diameter A rotates on the inner boundary of a circle with diameter 2A, then a point on the circumference of the inner circle traces a straight line.
As you can see, any point in the inner circumference is following a perfect linear movement. If we added up more points, we could trace their linear movement too.
See the Pen Article: Crazy illusion with Tusi motion #2 by Jaime Caballero (@jaicab) on CodePen.
But we’ll do the opposite, get a circular movement illusion from linear movements. I have created a .content
box which will act as the outer circumference. Inside of it, we’ll place tracks, the boundaries whithin the balls will move.
Placing the tracks
We’ll picture each .ball
element as a track where its before
pseudoelement will act as a ball. Since the balls are going to move all over the diameter, we only need to divide half the circumference (180deg). Each track will have its own angle and animation delay, so we’ll use the nth-child
selector combined with Sass to assign these properties:
@for $i from 1 to $num {
.ball:nth-child(#{$i}) {
transform: rotate((180deg * $i)/ (($num - 1)));
}
}
Our tracks are placed in the correct positions. Let’s start animating!
Animating the balls
We have to animate the before
pseudoelement of every ball, but again all of them have a different starting point so we can add it to de code above:
@for $i from 1 to $num {
.ball:nth-child(#{$i}) {
transform: rotate((180deg * $i)/ (($num - 1)));
&::before {
animation: ball
$speed
(($speed * $i)/ (($num - 1)))
cubic-bezier(0.4, 0, 0.6, 0.1)
alternate
infinite;
}
}
}
And that’s the key. The delay between balls must be always the same as the speed of the animation. An ease-in-out
cubic-bezier(.4, 0, .6, 1)
animation is required to get the circular shape. Otherwise it’d be deformed. If you’re wondering why I am substracting 1 from the $num
, it’s just because I added 1 to it so it worked directly in the @for
directive.
UPDATE: Even though the ease-in-out
function creates this circular illusion on Chrome, other browser have presented different behaviours for it. That’s why I’ve found the right cubic-bezier
values for it so it works the same across browsers.
Patching and fixing
If you had follow the steps, you must be facing a problem here. The balls take some time to form that circular shape and you don’t like that. Well, I solved that by animating the whole track with the ball within it:
@keyframes show {
5%,
40% {
background: transparent;
opacity: 1;
}
60%,
95% {
opacity: 1;
background: #222;
}
100% {
opacity: 1;
background: transparent;
}
}
First, I show the balls by setting the opacity to 1. After a while (from 5% to 40% of the time) the track gets a background, to make clear how the illusion works. It stays like that from 60% to 95% of the time and finally removes the background color from the track.
Then I set the animation for each track on the same code as before. It must last as long as the last track takes to finish its animation, that’s why it’ll always be $speed*($num - 1)
.
@for $i from 1 to $num {
.ball:nth-child(#{$i}) {
animation: show
$speed *
($num - 1)
(($speed * $i)/ (($num - 1)))
linear
forwards;
transform: rotate((180deg * $i)/ (($num - 1)));
&::before {
animation: ball
$speed
(($speed * $i)/ (($num - 1)))
cubic-bezier(0.4, 0, 0.6, 1)
alternate
infinite;
}
}
}
There’s also a Not Tusi motion which would be formed by spheres in motion creating a still figure. You can check it out here. I hope you enjoyed this explanation, if you have any questions please feel free to tweet me.