javascript - 如何根据当前pageYOffset和previousPageYOffset计算动画的速度(以秒为单位)?

标签 javascript reactjs styled-components

我正在尝试根据用户交互来旋转轮子。如果用户正在加速滚动,滚轮应该旋转得更快。同样,如果用户正在减速他的滚动,滚轮应该停止。我使用 Skrollr 库将这些条件与样式组件和 React 状态一起应用。
这是我所拥有的:

import React from "react";
import styled, { createGlobalStyle, css, keyframes } from "styled-components";

export default function App() {
  const [previousPosition, setPreviousPosition] = React.useState(0);
  const [previousDelta, setPreviousDelta] = React.useState(0);
  const [speed, setSpeed] = React.useState(1);
  const [isStopping, setIsStopping] = React.useState(false);

  React.useEffect(() => {
    skrollr.init();

    window.addEventListener("scroll", handleScroll);

    return () => window.removeEventListener("scroll", handleScroll);
  }, [previousPosition, previousDelta]);

  const handleScroll = React.useCallback(() => {
    // get delta
    const delta = window.pageYOffset - previousPosition;

    if (previousDelta > delta) {
      // is stopping
      setIsStopping(true);
      setSpeed(0);
    } else {
      // is accelerating

      // calculate delta as a percentage
      const deltaAsPercentage = (delta / previousPosition) * 100;
      console.log("deltaAsPercentage", deltaAsPercentage);

      setIsStopping(false);
      setSpeed(deltaAsPercentage);
    }

    setPreviousPosition(window.pageYOffset);
  }, [previousPosition, previousDelta]);

  return (
    <Container data-10000p="transform: translateX(0%)">
      <GlobalStyles />
      <WheelContainer speed={speed} isStopping={isStopping}>
        <Image src="wheel.png" alt="wheel" />
      </WheelContainer>
    </Container>
  );
}

const Container = styled.div`
  position: fixed;
  width: 100%;
  display: flex;
  top: 0;
  left: 0;
`;

const spinForward = keyframes`
    0% {
        transform: rotateZ(0deg);
    }
    50% {
        transform: rotateZ(180deg);
    }
    100% {
        transform: rotateZ(360deg);
    }
`;

const stopWheel = keyframes`
    0% {
        transform: rotateZ(0deg);
    }
    100% {
        transform: rotateZ(360deg);
    }
`;

const WheelContainer = styled.div`
  ${({ speed, isStopping }) =>
    isStopping
      ? css`
          animation: ${stopWheel} 1s ease-out;
        `
      : css`
          animation: ${speed
            ? css`
                ${spinForward} ${speed}s linear infinite
              `
            : ""};
        `}
`;

const Image = styled.img``;

const GlobalStyles = createGlobalStyle`
    * {
        &::-webkit-scrollbar {
            display: none;
        }
    }
`;
我数学不是很好,所以我正在尽我所能。
我在滚动时做的第一件事是确定轮子是否应该停止或加速。如果它正在停止,我改变组件的状态并让 WheelContainer知道它应该换出当前的动画。如果轮子不应该停止,我会保留当前动画并改变旋转速度。
无论如何,我已经得到了它的工作。我遇到的问题是它无法识别“较慢的滚动”。例如,用户可能正在快速或缓慢地滚动,但仍然在滚动。缓慢的滚动并不一定意味着它应该停止。
另一个问题是 spinBack似乎永远不会被调用。即使是这样,我也无法弄清楚如何区分“较慢”的滚动和向后旋转。
最后,我应该注意到加速滚动似乎只能在 Mac 的触控板上被识别。我只是插入了一个外接鼠标来测试它,它并没有像预期的那样旋转。
除此之外,我觉得还有更好的方法。也许与 Math.sin 相关或 Math.cos ,或者类似的东西,我应该在高中数学课上更加关注。当看起来有一种更简单的方法时,该组件感觉太笨重了。
这是一个沙盒,
priceless-snow-8wkjo

最佳答案

这是我的尝试:
对我来说,第一个问题是代码在输入输入时正在执行。感觉它需要一些时间延迟来计算函数。
我们将使用 setTimeout .
第二:是的,你是对的。我们需要一个类似数学/三 Angular 函数的函数,对于非常小的值,它的值接近于零,而对于增加的值,它的值接近于 1。
第三是……嗯,这更多是个人的事情——不确定这是否是故意的,但我注意到 spinBack一旦您滚动到顶部(即 window.pageYOffset = 0 ),该功能将不起作用。
所以,而不是 scroll事件监听器,我使用了 wheel eventListener — 这样,我可以使用 deltaY属性(property),看看它改变了多少。
第四,我将速度设置为时间所覆盖距离的函数。
最后:CSS 速度一开始对我来说是违反直觉的——出于某种原因, 的值越高。较慢 它旋转了!我一直想知道出了什么问题,直到我意识到我的愚蠢错误!😅
总结一下,我只会粘贴我更改的部分(我已在更改的地方添加了评论):

// == adding these
  var scrollTimeoutVar = null, scrollAmount = 0, totalScrollAmount = 0, scrollTimes = [];

  var theMathFunction = (x) => {
    return x/(2 + x);   // <- you can use a different value from '2' to determine how fast your want the curve to grow
  };

export default function App() {
  const [previousPosition, setPreviousPosition] = React.useState(0);
  const [previousDelta, setPreviousDelta] = React.useState(0);
  const [speed, setSpeed] = React.useState(1);
  const [isStopping, setIsStopping] = React.useState(false);
  const [isMovingForward, setIsMovingForward] = React.useState(true);

  React.useEffect(() => {
    skrollr.init();

    window.addEventListener("wheel", handleScroll); // <- changed from 'scroll'

    return () => window.removeEventListener("wheel", handleScroll); // <- changed from 'scroll'
  }, [previousPosition, previousDelta, isStopping]);

  const handleScroll = React.useCallback((wheelEvent) => {  // this time, we're passing in a wheel event
    // console.log("wheelEvent", wheelEvent);  // if you'd like to see it, uncomment

    // get deltaY, which will be our distance covered
    scrollAmount += wheelEvent.deltaY;  // this will be reset every time a new scroll is recorded
    totalScrollAmount += scrollAmount;  // this one will never be reset
    // add all the times that occured in the scroll
    scrollTimes.push((new Date()).getTime());
    setIsStopping(false);

    // adding this here, to cancel the setTimeout when the user is done
    if(scrollTimeoutVar !== null){
      clearTimeout(scrollTimeoutVar);
    };

    scrollTimeoutVar = setTimeout(() => {

      if(!isStopping){
        
        setIsStopping(true);
        // const delta = window.pageYOffset - previousPosition; <- no longer need this
        // get time difference
        const timeDiff = scrollTimes[scrollTimes.length - 1] - scrollTimes[0]; // when the scroll stopped - when it started


        // get direction
        // since our scrollAmount can either be positive or negative depending on the direction the user scrolled...
        // ...we can use the cumulative amount to determine direction:
        const isMovingForward =  totalScrollAmount > 0; // no longer window.pageYOffset > previousPosition;
        // console.log("isMovingForward", isMovingForward);  // if you'd like to see it, uncomment
        setIsMovingForward(isMovingForward);

        // calculate delta as a percentage
        // const deltaAsPercentage = (delta / previousPosition) * 100;  <- we no longer need this
        // setSpeed(deltaAsPercentage);  <- we no longer need this

        // our speed will be the simple physics equation of change in distance divided by change in time
        const current_speed = Math.abs(scrollAmount/timeDiff);   // we're using the 'absolute' function, so the speed is always a positive value regardless of the direction of scroll
        const trig_speed = theMathFunction(current_speed);       // give it a value between 0 and 1


        // here's the counter-intuitive part
        let speedToSet = 0;
        // if it's currently movingforward...
        if(isMovingForward){
          // should we go faster, or slower? in this case, subtract to go FASTER, add to go SLOWER
          speedToSet = scrollAmount > 0 ? (speed - trig_speed) : (speed + trig_speed);
        }
        // if not, it will be the reverse (because we're going in the OPPOSITE direction)
        else{
          // in this case, subtract to go SLOWER, add to go FASTER
          speedToSet = scrollAmount > 0 ? (speed + trig_speed) : (speed - trig_speed);
        }
        // console.log("speedToSet", speedToSet);  // if you'd like to see it, uncomment


        setSpeed(speedToSet);

        // set it back to zero, pending a new mouse scroll
        scrollAmount = 0;
        scrollTimes = [];

      }

    },100);
  }, [previousPosition, previousDelta, isStopping]);
使用上述方法,旋转将在用户滚动鼠标的任何方向上加速(并且在他们改变方向时会减速)。

关于javascript - 如何根据当前pageYOffset和previousPageYOffset计算动画的速度(以秒为单位)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65264259/

相关文章:

javascript - 检查网页是否实际使用 Javascript

javascript - 了解 XSockets.NET pubsub : producing and consuming messages from JavaScript

javascript - react-router-redux 将历史传递给中间件

styled-components - 带有@font-face 的隔离样式组件

javascript - 匹配标签内的部分文本

javascript - 如何在 React.JS 中有条件地添加路由?

reactjs - Mxgraph Reactjs 不导入 mxgraph 库

css - 如何使用带样式的组件创建上下文样式? (零选择器)

javascript - Reactjs 中的样式化组件

javascript - 从 url 中删除 bootstrap 3 选项卡 href 哈希名称