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 into x and y.
  • 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.