简体   繁体   English

如何在 React 的溢出 div 中向下和向上滚动无限 animation?

[英]How to have a scroll down and up infinite animation in an overflow div in React?

Basically, I have an overflow list of items with fixed height, so it's always be able to scroll.基本上,我有一个固定高度的项目溢出列表,所以它总是能够滚动。

So the overflow div will scroll down its content, for example in 4 seconds with 0.5 second delay then scroll up its content in 4 seconds with 0.5 second delay.因此溢出 div 将向下滚动其内容,例如在 4 秒内以 0.5 秒延迟然后在 4 秒内以 0.5 秒延迟向上滚动其内容。 It will be an infinite loop animation.这将是一个无限循环 animation。

The only thing that I know is to use scrollTop to make that works on react.我唯一知道的是使用 scrollTop 来使它起作用。

I'm using useAnimation from https://usehooks.com/useAnimation/ .我正在使用 https ://usehooks.com/useAnimation/ 中的 useAnimation

Below is my attempt.下面是我的尝试。 Probably my logic is wrong and need some improvements, currently it only scrolls down, scrolls up will break the code可能我的逻辑是错误的,需要一些改进,目前它只能向下滚动,向上滚动会破坏代码

Current attempt on codesandbox: https://codesandbox.io/s/useanimation-ks9nd当前对代码框的尝试: https://codesandbox.io/s/useanimation-ks9nd

import React, { useState, useEffect, useRef } from "react";
import ReactDOM from "react-dom";

function App() {
  const [scrollValue, setScrollValue] = useState(0);
  const scrollListRef = useRef(null);
  const [scrollState, setScrollState] = useState("down");
  const [scrollHeight, setScrollHeight] = useState(0);
  const [offsetHeight, setOffsetHeight] = useState(0);

  useEffect(() => {
    if (scrollListRef.current !== null) {
      setScrollHeight(scrollListRef.current.scrollHeight);
      setOffsetHeight(scrollListRef.current.offsetHeight);
    }
  }, []);
  useEffect(() => {
    if (scrollValue === 0) {
      setScrollState("down");
    } else if (scrollValue === scrollHeight - offsetHeight) {
      console.log("state up ne");
      setScrollState("up");
    }
  }, [offsetHeight, scrollHeight, scrollValue]);

  let durationTime = 4000; // seconds
  const animationDown = useAnimation("linear", durationTime / 2, 500);
  let animationUp = useAnimation("linear", durationTime / 2, 500);

  useEffect(() => {
    let scrollMax = scrollHeight - offsetHeight;
    if (scrollState === "down") {
      let value = animationDown * scrollMax;
      setScrollValue(value);
    } else if (scrollState === "up") {
      let value = scrollMax - animationUp * scrollMax;
      // setScrollValue(value)
    }
  }, [animationDown, animationUp, offsetHeight, scrollHeight, scrollState]);

  useEffect(() => {
    let ele = document.getElementById("locationsListScroll");
    ele.scrollTop = scrollValue;
  }, [scrollValue]);

  const lists = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
  let ulStyle = {
    padding: 0,
    position: "relative",
    marginLeft: "50%",
    marginTop: "3rem",
    transform: "translate(-25%)",
    maxHeight: 200,
    overflow: "auto"
  };
  let liStyle = {
    listStyleType: "none",
    height: 80,
    width: 80,
    display: "flex",
    border: "1px solid black",
    justifyContent: "center",
    alignItems: "center"
  };
  return (
    <div style={{ overflow: "hidden" }}>
      <ul
        ref={scrollListRef}
        id="locationsListScroll"
        className="row locations-list"
        style={ulStyle}
      >
        {lists.map((item, i) => (
          <li style={liStyle} key={i}>
            {item}
          </li>
        ))}
      </ul>
    </div>
  );
}
function useAnimationTimer(duration = 1000, delay = 0) {
  const [elapsed, setTime] = useState(0);

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

      // Function to be executed on each animation frame
      function onFrame() {
        setTime(Date.now() - start);
        loop();
      }

      // Call onFrame() on next animation frame
      function loop() {
        animationFrame = requestAnimationFrame(onFrame);
      }

      function onStart() {
        // Set a timeout to stop things when duration time elapses
        timerStop = setTimeout(() => {
          cancelAnimationFrame(animationFrame);
          setTime(Date.now() - start);
        }, duration);

        // Start the loop
        start = Date.now();
        loop();
      }

      // Start after specified delay (defaults to 0)
      const timerDelay = setTimeout(onStart, delay);

      // Clean things up
      return () => {
        clearTimeout(timerStop);
        clearTimeout(timerDelay);
        cancelAnimationFrame(animationFrame);
      };
    },
    [duration, delay] // Only re-run effect if duration or delay changes
  );

  return elapsed;
}

function useAnimation(easingName = "linear", duration = 500, delay = 0) {
  // The useAnimationTimer hook calls useState every animation frame ...
  // ... giving us elapsed time and causing a rerender as frequently ...
  // ... as possible for a smooth animation.
  const elapsed = useAnimationTimer(duration, delay);
  // Amount of specified duration elapsed on a scale from 0 - 1
  const n = Math.min(1, elapsed / duration);
  // Return altered value based on our specified easing function
  return easing[easingName](n);
}

// Some easing functions copied from:
// https://github.com/streamich/ts-easing/blob/master/src/index.ts
// Hardcode here or pull in a dependency
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))
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

The core problem of your logic is that the animationUp hook will immediately starts along with the animationDown .您的逻辑的核心问题是animationUp挂钩将立即与animationDown一起启动。

You said, that It will be an infinite loop animation , but as we all know hooks must be initialized on the root level of the component .你说,这将是一个无限循环 animation ,但众所周知,钩子必须在组件的根级别初始化 So we can't use your approach with two separate instance of the animation hooks.因此,我们不能将您的方法与 animation 挂钩的两个单独实例一起使用。 We need to somehow reset the animation hook every time when we reach the finish of the current animation.每次当我们到达当前 animation 的终点时,我们都需要以某种方式重置 animation 钩子。

Instead we can use the existing behavior of the useAnimationTimer dependency of re-initialization - Only re-run effect if duration or delay changes .相反,我们可以使用重新初始化的useAnimationTimer依赖项的现有行为 -只有在持续时间或延迟发生变化时才重新运行效果 It looks like a hack and this is something that can definitely must be improved.它看起来像一个黑客,这是绝对必须改进的。 And one more significant modification - add the animationState to the original animation timer, so it can give us the exact checkpoints of animation.还有一个更重要的修改——将animationState添加到原来的 animation 计时器中,这样它就可以为我们提供 animation 的确切检查点。

Working example : codesandbox工作示例codesandbox

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM