ideas
ideas copied to clipboard
useFrames: Keyframe animation using a generator function
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])
You might want to experiment with that API a whole bunch first
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.