Search code examples
javascriptcssreactjsstyled-components

How to make a box expand and transition from origin to center of screen without affecting DOM?


I'm trying to make it so that a box would expand (in width and height) and transition from its origin to the center of a screen upon being clicked. Here's what I have so far:

I'm running into two problems here -- when I click on the box, the DOM automatically shifts, because the clicked element has its position changed to 'absolute'. The other problem is that the box doesn't transition from its origin, it transitions from the bottom right corner (it also doesn't return to its position from the center of the screen, when make inactive is clicked).

What am I doing wrong here?

import React from "react";
import styled from "styled-components";
import "./styles.css";

export default function App() {
  const [clickedBox, setClickedBox] = React.useState(undefined);

  const handleClick = React.useCallback((index) => () => {
    console.log(index);
    setClickedBox(index);
  });

  return (
    <Container>
      {Array.from({ length: 5 }, (_, index) => (
        <Box
          key={index}
          active={clickedBox === index}
          onClick={handleClick(index)}
        >
          box {index}
          {clickedBox === index && (
            <div>
              <button
                onClick={(e) => {
                  e.stopPropagation();
                  handleClick(undefined)();
                }}
              >
                make inactive
              </button>
            </div>
          )}
        </Box>
      ))}
    </Container>
  );
}

const Container = styled.div`
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  height: 100vh;
`;

const Box = styled.div`
  flex: 1 0 32%;
  padding: 0.5rem;
  cursor: pointer;
  margin: 1rem;
  border: 1px solid red;
  transition: 2s;
  background-color: white;
  ${({ active }) => `
    ${
      active
        ? `
      position: absolute;
      width: 50vw;
      height: 50vh;
      background-color: tomato;
      top: 50%;
      left: 50%; 
      transform: translate(-50%, -50%);
    `
        : ""
    }
  `}
`;

relaxed-galois-iypsj


Solution

  • The idea is similar to what newbie did in their post but without any extra libraries. I might have done some things a bit non-standard to avoid using any libraries.

    CodeSandbox

    import React from "react";
    import { StyledBox } from "./App.styles";
    
    export const Box = (props) => {
      const boxRef = React.useRef(null);
      const { index, active, handleClick } = props;
      const handleBoxClick = () => {
        handleClick(index);
      };
    
      React.useEffect(() => {
        const b = boxRef.current;
        const a = b.querySelector(".active-class");
        a.style.left = b.offsetLeft + "px";
        a.style.top = b.offsetTop + "px";
        a.style.width = b.offsetWidth + "px";
        a.style.height = b.offsetHeight + "px";
      });
    
      return (
        <StyledBox active={active} onClick={handleBoxClick} ref={boxRef}>
          box {index}
          <div className="active-class">
            box {index}
            <div>
              <button
                onClick={(e) => {
                  e.stopPropagation();
                  handleClick(undefined);
                }}
              >
                make inactive
              </button>
            </div>
          </div>
        </StyledBox>
      );
    };
    
    
    import styled from "styled-components";
    
    export const StyledContainer = styled.div`
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      height: 100vh;
    `;
    
    export const StyledBox = styled.div`
      flex: 1 0 32%;
      padding: 0.5rem;
      cursor: pointer;
      margin: 1rem;
      border: 1px solid red;
    
      background-color: white;
    
      .active-class {
        position: absolute;
        transition: 0.3s all ease-in;
        background-color: tomato;
        z-index: -1;
    
        ${({ active }) =>
          active
            ? `
          width: 50vw !important;
          height: 50vh !important;
          top: 50% !important;
          left: 50% !important; 
          transform: translate(-50%, -50%);
          z-index: 1;
          opacity: 1;
        `
            : `
          z-index: -1;
          transform: translate(0, 0);
          opacity: 0;
        `}
      }
    `;