Circular motion
HTML and SVG elements are positioned by x
and y
coordinates. This type of positioning is known as cartesian coordinates, and animating these is great for moving elements in straight lines (the majority use-case).
However, to move elements in a circular fashion, it’s much easier to animate position using polar coordinates: Position as defined by angle
and radius
.
We can then convert these polar coordinates into x
and y
to produce movement like this:
In this quick tutorial, we’ll create circular motion converting polar to cartesian coordinates using functional composition.
As a bonus step, we’ll then explain how we can rotate the element to face its direction of travel.
You can fork this CodePen to follow along.
Position
We can convert radius
and angle
into x
and y
using cos
and sin
functions:
const x = radius * Math.cos(angle);
const y = radius * Math.sin(angle);
By expressing this as a pure function, we’ll be able to provide it to any animation’s pipe
method:
const polarToCartesian = ({ angle, radius }) => ({
x: radius * Math.cos(angle),
y: radius * Math.sin(angle)
});
With this function we can write a simple animation that:
- 1) Changes
radius
at a constant velocity of 5 radians a second. - 2) Pipes the animation output through
polarToCartesian
to convert intox
andy
. - 3) Styles the div by using
boxStyler.set
:
physics({
from: { angle: 0, radius: 150 },
velocity: { angle: 5, radius: 0 }
}).pipe(polarToCartesian)
.start(boxStyler.set);
Now our box moves in a circular motion.
In this animation we’re keeping radius
at a constant value by setting velocity
to 0
. By using the composite
function, we can combine two different animations to animate radius
and angle
in different ways:
composite({
angle: physics({ velocity: 5 }),
radius: tween({
from: 0,
to: 150,
yoyo: Infinity,
ease: easing.easeInOut,
duration: 2000
})
}).pipe(polarToCartesian)
.start(boxStyler.set);
Direction
It looks a little awkward to have a square stay upright as it moves in a circular motion.
We can calculate and inject a rotate
property based on the current angle
to ensure the square is facing the direction of travel.
First, let’s make our polarToCartesian
function more composable by allowing it to pass through any properties that it doesn’t consume using spread props:
const polarToCartesian = ({ angle, radius, ...props }) => ({
x: radius * Math.cos(angle),
y: radius * Math.sin(angle),
...props
});
Next, we need to make a function that simply takes angle
and returns the angle perpendicular to it as a new property, rotate
.
cos
and sin
functions accept radians, whereas CSS and SVG rotate
properties are defined in degrees. We can use Popmotion’s radiansToDegrees
calculator to convert angle
into degrees, and then simply rotate it by 90
:
const rotatePerpendicular = (props) => {
const { angle } = props;
return {
rotate: radiansToDegrees(angle) + 90,
...props
};
};
Applying this new function is as easy as modifying pipe
to read:
.pipe(rotatePerpendicular, polarToCartesian)
Conclusion
Circular motion is much easier to reason about in polar coordinates, and mapping these to cartesian is simple with pipe
.
We can animate angle
and radius
with separate animations by using the composite
composition function.
And finally, we can make the element rotate along with the direction of travel by converting angle
into degrees and then adding an extra 90
degrees.