useAnimationOnDidUpdate反应挂钩实现

时间:2020-04-01 09:09:07

标签: javascript reactjs react-hooks requestanimationframe

所以我想使用requestAnimationFrame通过react hooks为某些东西设置动画。
我想要一些小的东西,所以react-spring / animated / react-motion对我来说是一个糟糕的选择。
我实现了useAnimationOnDidUpdate,但是它无法正常工作,这里是reproduction,其中包含详细信息。
出问题了:在第二次点击时动画的乘法器以1开头,但应始终以0开头(从0到1的简单插值)。
因此,我试图理解为什么即使我已经开始新的动画循环,该钩子为什么仍保存了先前的值。
这是钩子的完整代码清单:

import { useState, useEffect, useRef } from 'react';

export function useAnimationOnDidUpdate(
  easingName = 'linear',
  duration = 500,
  delay = 0,
  deps = []
) {
  const elapsed = useAnimationTimerOnDidUpdate(duration, delay, deps);
  const n = Math.min(1, elapsed / duration);

  return easing[easingName](n);
}

// https://github.com/streamich/ts-easing/blob/master/src/index.ts
const easing = {
  linear: n => n,
  elastic: n =>
    n * (33 * n * n * n * n - 106 * n * n * n + 126 * n * n - 67 * n + 15),
  inExpo: n => Math.pow(2, 10 * (n - 1)),
  inOutCubic: (t) => t <.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1,
};

export function useAnimationTimerOnDidUpdate(duration = 1000, delay = 0, deps = []) {
    const [elapsed, setTime] = useState(0);
    const mountedRef = useRef(false);

    useEffect(
        () => {
            let animationFrame, timerStop, start, timerDelay;

            function onFrame() {
                const newElapsed = Date.now() - start;

                setTime(newElapsed);
                if (newElapsed >= duration) {
                    console.log('>>>> end with time', newElapsed)
                    return;
                }
                loop();
            }

            function loop() {
                animationFrame = requestAnimationFrame(onFrame);
            }

            function onStart() {
                console.log('>>>> start with time', elapsed)
                start = Date.now();
                loop();
            }

            if (mountedRef.current) {
                timerDelay = delay > 0 ? setTimeout(onStart, delay) : onStart();
            } else {
                mountedRef.current = true;
            }

            return () => {
                clearTimeout(timerStop);
                clearTimeout(timerDelay);
                cancelAnimationFrame(animationFrame);
            };
        },
        [duration, delay, ...deps]
    );

    return elapsed;
}

1 个答案:

答案 0 :(得分:1)

此钩子存在的问题是它在完成时不会清理elapsedTime

您可以通过在动画预计停止时向您的setTime(0)函数添加onFrame来解决此问题。

赞:

function onFrame() {
  const newElapsed = Date.now() - start;

  if (newElapsed >= duration) {
    console.log('>>>> end with time', newElapsed)
    setTime(0)
    return;
  }

  setTime(newElapsed);
  loop();
}

我知道它没有重置自己似乎很奇怪。但是请记住,您的动画使用相同的钩子实例来进行缓入和缓出。因此,清理是必要的。

注意:我还移动了setTime(newElapsed)行,使其位于if语句之后,因为如果if语句为true,则不需要这样做。

更新:

要进一步改善其工作原理,可以将setTime(0)移至return清理。

这意味着您将onFrame的功能更改为:

function onFrame() {
  const newElapsed = Date.now() - start;

  if (newElapsed >= duration) {
    console.log('>>>> end with time', newElapsed)
    setTime(0)
    return;
  }

  setTime(newElapsed);
  loop();
}

然后将return的{​​{1}}清理更新为:

useAnimationTimerOnDidUpdate

我假设您的动画“无法正常工作”的原因是该组件会闪烁。就我的测试而言,此更新可以解决该问题。