useAnimationOnDidUpdate 反应挂钩实现

useAnimationOnDidUpdate react hook implementation

所以我想 requestAnimationFrame 使用 React hooks 来制作动画。
我想要一些小东西,所以 react-spring/animated/react-motion 对我来说是个糟糕的选择。
我实施了 useAnimationOnDidUpdate 但它工作不正确,这里是 reproduction 的详细信息。
这里有什么问题:在第二次点击时,动画的乘数从 1 开始,但应该始终从 0 开始(从 0 到 1 的简单插值)。
所以我试图理解为什么钩子保存了以前的值,尽管我已经开始了一个新的动画循环。
这是 hook 的完整代码清单:

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;
}

此挂钩的问题在于它不会在完成后清理 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 语句则不需要这样做是真的。

更新:

要进一步改进其工作方式,您可以将 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();
}

然后将 useAnimationTimerOnDidUpdatereturn 清理更新为:

return () => {
  clearTimeout(timerStop);
  clearTimeout(timerDelay);
  cancelAnimationFrame(animationFrame);
  setTime(0);
};

我假设你的动画 "isn't working properly" 是因为组件会闪烁。就我的测试而言,此更新修复了该问题。