Physics and velocity

A core feature of Popmotion is the ability for some animations (decay, spring and physics) to take a velocity parameter.

velocity changes each of these animations in a way that feels natural and visceral.

By passing the velocity of an existing animation to another, we can create natural-feeling transitions. And if that existing animation is pointer, it allows the user to directly affect animations with their own force leading to natural and playful interactions.

Inspect velocity

But how do we get velocity?

Popmotion provides a special type of reaction called value.

import { value } from 'popmotion';

value sits between your action and another reaction (for instance a style setter), and can be queried with get and getVelocity:

const myValue = value(0, console.log);

tween().start(myValue);

setTimeout(() => myValue.getVelocity(), 100)

The returned velocity is measured in units per second. Why? Although 60fps is the current common framerate, VR devices support 90+ fps and the iPad Pro delivers a silky 120 frames per second!

To future-proof our code, we decouple velocity from the device framerate, otherwise our animations would run at 1.5x or even 2x the speed on these faster displays.

Using value

value is provided two arguments: A value, and a function to call when this value updates:

const foo = value(0, console.log);

As value is a reaction, it has an update method. We can call it to update the value:

foo.update(5);

Usually though, we provide the value directly to an action:

tween({ to: 5 }).start(foo);

Like our animations, value can accept objects and arrays:

const xy = value({ x: 0, y: 0 }, console.log);
const foo = tween({
  to: { x: 100, y: 200 }
}).start(xy);

setTimeout(() => xy.getVelocity(), 100); // Returns as object

Now we know enough about value to get the velocity of our user’s pointer.

Get pointer velocity

Using the example from the previous tutorial, let’s first make a value that updates ballStyler‘s x and y properties:

const ballXY = value({ x: 0, y: 0 }, ballStyler.set);

Now we can replace our startTracking function with this:

const startTracking = () => {
  pointer(ballXY.get()).start(ballXY);
};

As an added benefit of using value, a value can’t be subscribed to more than one action at a time.

This means that we can stop using pointerTracker to maintain a reference to our active pointer.

Instead, we can either use ballXY.stop(), which will stop the action it’s currently subscribed to. Or, we simply provide it to a different action, which is what we’ll do in the following examples.

For now, amend stopTracking so it queries ballXY‘s current velocity:

const stopTracking = () => {
  const velocity = ballXY.getVelocity();
};

Using velocity

Three Popmotion animations accept a velocity property: decay, physics and spring.

Let’s modify stopTracking three times, once for each, to see what they each do with velocity.

decay

decay exponentially decreases a velocity over time. It’s a form of the algorithm used in smartphone momentum scrolling, making it a natural-feeling way of slowing something down.

Based on the initial properties and velocity, it’ll automatically calculate a target to animate to.

Using it is as easy as passing our newly-calculated velocity to decay:

decay({ velocity }).start(ballXY);

decay also accepts a modifyTarget function, which is provided the calculated target and returns a new one.

This can be used, for instance, to snap the target to a grid:

decay({
  from: ballX.get(),
  velocity,
  modifyTarget: (target) => Math.ceil(target / 100) * 100
})

spring

spring is a spring simulation using mass, velocity, stiffness and damping. It can be used to simulate a wide variety of spring-feels.

Springs are great for interaction designers because they’re expressive. For instance, you could design a spring that has a stiffness and damping property, but the mass property is based on the relative size of the element moving.

spring has defaults for all properties but you’ll likely want to adjust at least stiffness and damping:

spring({
  from: ballXY.get(),
  velocity,
  stiffness: 300,
  damping: 10
}).start(ballXY);

physics

The physics animation is the swiss army knife of velocity-based animations.

It offers friction, to and springStrength properties, so it can theoretically be used to create motion similar to decay and spring.

However, decay and spring animations are differential equations that resolve for a given elapsedTime. In practical terms, this means that if you want to change the animation they’re creating, you need create a new animation with the new properties.

These equations are incredibly accurate, offering the smoothest motion and in the near future, will allow these animations to be scrubbable the same way tween is.

Instead, physics is an integrated simulation. This means that, once the simulation has started, its properties can be modified because physics uses its current state to calculate its next

For instance, we can tether a physics spring between the ball and the pointer:

const springTo = physics({
  velocity: ballXY.getVelocity(),
  friction: 0.6,
  springStrength: 400,
  to: ballXY.get(),
  restSpeed: false
}).start(ballXY);

pointer(ballXY.get())
  .start((v) => springTo.setSpringTarget(v));

Conclusion

velocity is a key part of creating natural interactions with Popmotion.

Be sure to check out the full docs of value, decay, spring and physics, as there’s much more to each than we’ve been able to cover in this introductory tutorial.