Search code examples
cssreactjscss-animationsstyled-components

how to use a styled component to play an animation on scroll in react


I've been trying to use styled-component animations to make my navbar change color upon scrolling down and returning it to the original color when I scroll back up. the only issue I'm currently facing is that I have no idea how to only trigger it when you scroll down the page and return it back to normal when I hit the top again.

const ChangeColor = keyframes`
0%{
  background-color: #121314;
}
100%{
    background-color: white;
}
`;

const GeneralContainer = styled.body`
  background-color: #121314;
  height: 10000px;

  margin: 0;
`;

const NavBarContainer = styled.div`
  border: 1px green solid;
  height: 64px;
  position: fixed;
  width: 100%;
  animation: ${NavForward} 1s linear alternate forwards 1; //I just want this to fire once when I scroll downward and fire again when I return to the top of the page.
`;

export default function Home() {
  const [offset, setOffset] = useState(0);
  const [isUpper, setIsUpper] = useState(true); // was trying to use this to trigger but couldnt

  useEffect(() => {
    const onScroll = () => {
      setOffset(window.pageYOffset);
      if (window.pageYOffset > 30 && isUpper === true) {
        setIsUpper(false);
      } else if (window.pageYOffset <= 30 && isUpper === false) {
        setIsUpper(true);
      }
    };
    // clean up code
    window.removeEventListener("scroll", onScroll);
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);

  return (
    <GeneralContainer>
      <NavBarContainer
        isUpper={isUpper}
        scoll={offset}
      ></NavBarContainer>
    </GeneralContainer>
  );
}

Solution

  • I would not use an animation for this personally. Adding and removing classes is an easier path.

    What you want to do is similar to what you were already doing, but basically set up another effect to listen to when the Y offset has gone past your arbitrary height.

    First, set up a function that we'll call in our effect that deals with the scroll:

    const handleScroll = () => setOffset(window.pageYOffset);
    

    Then create a dedicated effect to deal with the scroll:

    useEffect(() => {
      window.addEventListener("scroll", handleScroll);
      return () => {
        window.removeEventListener("scroll", handleScroll);
      };
    }, []);
    

    Then, in another effect we'll listen to see if we've gone past that amount of pixels to hide/show the background colour:

    useEffect(() => {
      if (offset > 100) {
        setIsUpper(false);
      } else {
        setIsUpper(true);
      }
    }, [offset]);
    

    With that we need to let the styled component know when things have changed. Using a css class is good for the job here:

    <NavBarContainer className={isUpper ? "active" : ""}></NavBarContainer>
    

    And then in your NavBarContainer styled component, just add a class making sure to use a transition instead of an animation:

    const NavBarContainer = styled.div`
      // ... the rest of your styles
      transition: 1s ease;
    
      &.active {
        background-color: white;
      }
    `;
    

    All together that should work. Check out the sandbox here. for a working example.

    Also, I've changed your GeneralContainer component to use a div as it's HTML tag instead of a body tag. This is because your React app is mounted to a div and nesting a body within a body is technically invalid HTML.