designsystemsinternational / mechanic

Mechanic is a framework to build assets built on web code.
https://mechanic.design
MIT License
243 stars 11 forks source link

Add timestamp to frameloop #169

Closed lucasdinonolte closed 1 year ago

lucasdinonolte commented 1 year ago

TL;DR

This calculates the timestamp of the current frame based on the current frame number and the framerate and sends this information to the design function via the newly added drawLoop (or useDrawLoop) helpers.

Context

Thinking of animation as a series of frames is great from a technical standpoint, but not always easy to wrap your head around as someone building the animation. Additionally, depending on your framerate the exact same frame can have a different number within the same animation. (The 1 second frame is frame 60 at 60fps, but only 24 at 24 fps).

A more natural way to think about this is the time offset of a frame. So referring to a frame of animation to be the frame at 1 seconds time offset for example.

To add this ability to the new Animation API propose passing both the frameCount and the timestamp (in seconds, see below) to the callback a user provides to drawLoop. For React this means the return value of useDrawLoop can be destructured to { frameCount, timestamp }.

Timestamp calculation is simple if you know the framerate and the number of the current frame. To get the offset in seconds means to divide the current frame count by the framerate. All of these information are available in the newly added drawLoop util of the new animation API, so calculating the timestamp is just one extra line of code there.

Why seconds: I chose to go with seconds (instead of milliseconds), as seconds feel more natural to understand. Sub-second intervals could still be specified by using floating point numbers throughout your code.

What this does

What does this mean?

In your design functions you can now do this:

// frameCount has the number of the current frame, this is based on the framerate
// your animation is running it. For 60fps the frame at 1 seconds will be 60, while
// at 24 fps it will be 24.
//
// timestamp has the frame offset in seconds and is always the same, no matter the
// framerate.
drawLoop(({ frameCount, timestamp }) => {
  // You can now use the timestamp (in seconds) to determine animation progress
  // or define the finalization criteria of your animation

  // This will update the angle, so for each second there is 180° of rotation, no matter the frameRate
  const rotationAngle = 180 * timestamp;

  // Finalize after 10 seconds
  if (timestamp > 10) mechanic.done();
});

Why is this cool?

As you know, I'm a big fan of frame based animation. At least, that's what I thought. I came to realize that actually I'm a huge fan of what I'd call "deterministic animation", where each frame is a pure function. It took me some time to understand that this isn't strictly tied to using frame numbers for animation. IMHO this addition to the new Animation API makes it easier to write animation frames as pure functions while working with "mental categories" (read "seconds") that you're familiar with from everyday life.