Wednesday, 16 December 2020

How to calculate the speed of an animation in seconds based on current pageYOffset and previousPageYOffset?

I'm trying to rotate a wheel based on user interaction. If the user is accelerating his scroll, the wheel should spin faster. Likewise, if the user is decelerating his scroll, the wheel should stop. I'm applying these conditions with styled-components and React state, using the Skrollr library.

Here is what I have:

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

I'm not great at math, so I'm doing my best with what I know.

The first thing that I do on scroll is determine whether or not the wheel should be stopping or accelerating. If it's stopping, I alter the state of the component and let WheelContainer know that it should swap out the current animation. If the wheel shouldn't be stopping I keep the current animation and alter the speed of the rotation.

Anyway, I've got it kind of working. The issue that I'm running into is that it doesn't recognize a "slower scroll". For instance, the user could be scrolling quickly or slowly but scrolling nonetheless. A slow scroll shouldn't necessarily mean that it should come to a stop.

The other issue is that spinBack seems to never be invoked. And even if it was, I'm having trouble figuring out how I'd be able to differentiate between a "slower" scroll and a backward spin.

Finally, I should note that the accelerated scroll seems to be recognized only on a mac's trackpad. I just plugged in an external mouse to test it out, and it doesn't really quite rotate as expected.

In addition to this, I feel like there is a better approach to this. Perhaps something with Math.sin or Math.cos, or something similar that I should've been paying more attention to in high school math class. The component just feels too bulky, when it seems like there is a much more simple approach.

Here's a Sandbox,

priceless-snow-8wkjo



from How to calculate the speed of an animation in seconds based on current pageYOffset and previousPageYOffset?

No comments:

Post a Comment