Search code examples
cssreactjsstyled-components

How to reverse animation when prop is false styled components


I am passing a open prop to styled component to create animations for the hamburger icon. This is the code

const StyledBurger = styled.button`
  display: flex;
  flex-direction: column;
  justify-content: center;
  border: 0;
  background-color: ${colors.cobaltBlue};
  border-radius: 2.7px;
  cursor: pointer;
  div {
    width: 27px;
    height: 3px;
    margin: 1.5px;
    transition: all 0.2s linear;
    border-radius: 1.4px;
    background-color: ${colors.white};
    :first-child {
      ${({ open }) => open && firstOpenAnimation};
    }
    :nth-child(2) {
      opacity: ${({ open }) => (open ? '0' : '1')};
    }
    :nth-child(3) {
      ${({ open }) => open && seconOpenAnimation}
    }
  }
`;

const firstOpenKeyframe = keyframes`
  50% {
    transform: translateY(6px) rotate(0);
  }
  100% {
    transform: translateY(6px) rotate(45deg);
  }
`;

const secondOpenKeyframe = keyframes`
  50% {
    transform: translateY(-6px) rotate(0);
  }
  100% {
    transform: translateY(-6px) rotate(-45deg);
  }
`;

const firstCloseKeyFrame = keyframes`
  50% {
    transform:translateY(0) rotate(-45deg);
  }
  100% {
    transform:translateY(-6px) rotate(-45deg)  ;
  }
`;

const firstOpenAnimation = css`
  animation: 0.3s linear ${firstOpenKeyframe} forwards;
`;

const seconOpenAnimation = css`
  animation: 0.3s linear ${secondOpenKeyframe} forwards;
`;

const firstCloseAnimation = css`
  animation: 0.3s linear ${firstCloseKeyFrame} forwards;
`;

export default StyledBurger;

Basically what I want is if the menu is not open to reverse the animation that was created after the first click. I tried doing a conditional render of animation keyframe based on the prop open but what happens is when the page loads it immediately creates the animation of not is opened because it satisfies false. What can I do to fix this and create the opposite animation when unClicked


Solution

  • Make few corrections and it should all work properly.

    • use state to toggle open and send it as prop to your styled component
    • use ternary for animation (not just &&) ${({ open }) => (open ? firstOpenAnimation : firstCloseAnimation)}.
    • implement missing close second animation

    working copy of your code is here

    Working code snippet

    const StyledBurger = styled.button`
      display: flex;
      flex-direction: column;
      justify-content: center;
      border: 0;
      background-color: red;
      border-radius: 2.7px;
      cursor: pointer;
      height: 30px;
      div {
        width: 27px;
        height: 3px;
        margin: 1.5px;
        transition: all 0.2s linear;
        border-radius: 1.4px;
        background-color: white;
        :first-child {
          ${({ open }) =>
            open !== null && (open ? firstOpenAnimation : firstCloseAnimation)}
        }
        :nth-child(2) {
          opacity: ${({ open }) => (open ? "0" : "1")};
        }
        :nth-child(3) {
          ${({ open }) =>
            open !== null && (open ? seconOpenAnimation : secondCloseAnimation)}
        }
      }
    `;
    
    const firstOpenKeyframe = keyframes`
      50% {
        transform: translateY(6px) rotate(0);
      }
      100% {
        transform: translateY(6px) rotate(45deg);
      }
    `;
    
    const secondOpenKeyframe = keyframes`
      50% {
        transform: translateY(-6px) rotate(0);
      }
      100% {
        transform: translateY(-6px) rotate(-45deg);
      }
    `;
    
    const firstCloseKeyFrame = keyframes`
      50% {
        transform:translateY(0) rotate(-45deg);
      }
      100% {
        transform:translateY(0) rotate(0)  ;
      }
    `;
    const secondCloseKeyFrame = keyframes`
      50% {
        transform:translateY(0) rotate(-45deg);
      }
      100% {
        transform:translateY(0) rotate(0)  ;
      }
    `;
    
    const firstOpenAnimation = css`
      animation: 0.3s linear ${firstOpenKeyframe} forwards;
    `;
    
    const seconOpenAnimation = css`
      animation: 0.3s linear ${secondOpenKeyframe} forwards;
    `;
    const secondCloseAnimation = css`
      animation: 0.3s linear ${secondCloseKeyFrame} forwards;
    `;
    
    const firstCloseAnimation = css`
      animation: 0.3s linear ${firstCloseKeyFrame} forwards;
    `;
    export default function App() {
      const [open, setOpen] = useState(null);
    
      return (
        <div className="App">
          <h1>Hello CodeSandbox</h1>
          <StyledBurger onClick={() => setOpen(prev => !prev)} open={open}>
            <div />
            <div />
            <div />
          </StyledBurger>
        </div>
      );
    }