ideas icon indicating copy to clipboard operation
ideas copied to clipboard

useFrames: Keyframe animation using a generator function

Open RoyalIcing opened this issue 7 years ago • 2 comments

Generator functions work nicely for keyframe animation. Here is an idea for an API, where the value for each frame are yielded. Once the generator is done, then requestAnimationFrame will no longer be scheduled. Dependency value are passed in as a third argument, which causes the generator function to be re-run.

(written on my iPad sorry, will have a laptop again in 48 hours)

The example here is a simple linear animation, but you can imagine more complex maths for easing, etc:

let [animateChange, setAnimateChange] = useState(0)
const animateForward = () => setAnimateChange(1)
const animateBack = () => setAnimateChange(-1)
const pause = () => setAnimateChange(0)

let x = useFrames(0, function *(f) {
  if (animateChange === 0) {
    return
  }

  while (f > 0 && f < 20) {
    f += animateChange
    yield f
  }
}, [animateChange])

RoyalIcing avatar Oct 25 '18 21:10 RoyalIcing

You might want to experiment with that API a whole bunch first

jamiebuilds avatar Oct 25 '18 22:10 jamiebuilds

I have prototyped a useFrames(generatorFunction, playing):

Example

https://codesandbox.io/s/github/BurntCaramel/react-hooks-examples (See Logo in App.js)

const [animated, setAnimated] = useState(true);

const r = useFrames(function *(t) {
  for (;;) {
    t = yield 50 + ((Math.cos(t / 1000) * 20));
  }
}, animated)

Implementation

import { useState, useEffect, useMemo } from "react";

export default function useFrames(generatorF, playing) {
  const generator = useMemo(() => {
    if (playing) {
      return generatorF(Date.now());
    }
    else {
      return null;
    }
  }, [playing]);

  const [value, changeValue] = useState();

  useEffect(() => {
    if (generator) {
      const stopValue = {};

      function nextFrame(t) {
        const { value, done } = generator.next(t);
        if (!!value && value !== stopValue) {
          changeValue(value);
        }
    
        if (!done) {
          requestAnimationFrame(nextFrame);
        }
      }

      nextFrame(Date.now());

      return () => {
        generator.return(stopValue);
      }
    }
  }, [generator]);

  return value;
}

Any feedback appreciated.

RoyalIcing avatar Oct 30 '18 13:10 RoyalIcing