designsystemsinternational / mechanic

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

[WIP] Animation Utilities #177

Open lucasdinonolte opened 1 year ago

lucasdinonolte commented 1 year ago

This is a little something that was in the back of my head for some time – but it's only for after the new animation API is in.

Context

I've found myself copy & pasting over code to handle non-linear interpolation (aka easing) for animation over and over again. So I had this idea it could be cool if mechanic provided a simple way to use the most common easing functions without having to re-code them yourself.

What this does

This adds a @mechanic-design/transition package that exposes a generic interpolation helper to the user. It also comes with pre-built versions of the most commonly used easing functions for quick access. But users’ can also bring their own easing function if they wanted to.

The transition function is setup as a curried function, to encourage a clean usage based on animation lifecycle events—the transition should be set up when setting up your design function and then in the draw loop you only pull values from it.

Examples

The most basic example, animating a rectangle’s position:

import { transition } from "@mechanic-design/transition";

export const handler = async ({ inputs, frame, done, getCanvas, drawLoop }) => {
  const { width, height } = inputs;

  const { ctx } = getCanvas(width, height);
  const xPos = transition({
    from: width * 0.3,
    to: width * 0.7,
    duration: 2,

    // Allows to set how often the transition should run
    // Defaults to 1
    iterationCount: 10,

    // Defines the direction of the animation
    // forward (0 -> 1)
    // runs the animation in the specified direction
    //
    // reverse (1 -> 0)
    // runs the animation in the reversed direction 
    //
    // alternate (0 -> 1 -> 0 -> 1 -> ...)
    // runs the animation in alternating directions, starting
    // with a forwards run.
    //
    // alternateReverse (1 -> 0 -> -> 0 -> ...)
    // runs the animation in alternating directions, starting
    // with a reversed run.
    //
    // Defaults to forward
    direction: 'alternate',

    // A user can go for one of the pre-built easing functions
    // by referencing its name
    easing: 'easeInOutQuad',
  });

  drawLoop(({ timestamp }) => {
    ctx.clearRect(0, 0, width, height);

    ctx.fillStyle = "blue";
    ctx.fillRect(xPos(timestamp), height / 2 - 50, 100, 100);

    if (timestamp < 2) {
      frame();
    } else {
      done();
    }
  });
};

Using a custom easing function:

import { transition } from "@mechanic-design/transition";

export const handler = async ({ inputs, frame, done, getCanvas, drawLoop }) => {
  const { width, height } = inputs;

  const { ctx } = getCanvas(width, height);
  const xPos = transition({
    from: width * 0.3,
    to: width * 0.7,
    duration: 2,

    // Easing functions are called with a linear t value below 0 and 1
    // and map this value to whatever easing curve you want to approximate
    //
    // The example below maps to a cubic curve
    easing: t => t * t * t
  });

  drawLoop(({ timestamp }) => {
    ctx.clearRect(0, 0, width, height);

    ctx.fillStyle = "blue";
    ctx.fillRect(xPos(timestamp), height / 2 - 50, 100, 100);

    if (timestamp < 2) {
      frame();
    } else {
      done();
    }
  });
};

The transition helper only works with numerical values. You could however still use it to interpolate non numerical values (like color), by using the transition helper to generate the easing curve for you and then passing a normalized transition value to a specific interpolation function. The example shows this

const colorEase = transition({
    from: 0,
    to: 1,
    duration: 2,
    easing: 'easeInOutQuad',
});

drawLoop(({ timestamp }) => {
  // The imaginary colorInterpolator function would be a custom function
  // (user-written or from a library) that interpolates two colors based on
  // a 0–1 value. The transition helper is used here to put the easing curve
  // on the 0–1 scale.
  const color = colorInterpolator(color1, color2, colorEase);

  ctx.fillStyle = color;
});
netlify[bot] commented 1 year ago

Deploy Preview for dsi-logo-maker ready!

Name Link
Latest commit 564f6e537c5653aa26efc893f2c7451683040dfe
Latest deploy log https://app.netlify.com/sites/dsi-logo-maker/deploys/64649e3d1d53df0008230470
Deploy Preview https://deploy-preview-177--dsi-logo-maker.netlify.app
Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.