Home > Enterprise >  having a flex child scale and cover all other children inside container on click
having a flex child scale and cover all other children inside container on click

Time:07-08

I have a flex container with some children that are <divs>. onClick I want the clicked cube to scale and animate to fill all the available width and height, but where I'm stuck is I would also like it to overlap all the other children so that it ends up being the only one visible.

I want it to break out of the document flow with I assume position:absolute but I also want it to preserve its starting coordinates as it expands, so setting any top or other property is not my desired result

How is this achievable? Sandbox is linked below

Sandbox

CodePudding user response:

You can check the solution here link to sandbox. Used position absolute but not the top property as you wanted. I have pasted the code below for quick reference. Removed styles for item inside your css since you are providing it inside the component. I have also added key attribute to the li element as React would want you to add that. To not cause multiple useEffect() calls I have added [expand] dependency.

Latest edit: It looked like the earlier solution worked only if the height of the flex container is less than the width. So if you resize the screen such that its height becomes greater than the width, the solution fails. So to solve that, I have updated the code below and also the sandbox where you can checkout by changing screen sizes. Depending on whether the height of the flex container is greater than its width or not, you have to adjust the height properties as you can see in the code. You check that condition inside useEffect. I have commented the added codes as well. That's it!

Edit: How does it work?

When you click on the child element(li), it is absolutely positioned but relative to its parent container so it gets automatically positioned to the top-left corner of the parent ul.

Since the container width is set to 100vw, it takes the full width of the viewport and that becomes the min-width of the itemExpand since we want it's width to cover the container. Now due to aspectRatio:'1', the height of the itemExpand will be equal to its width but we don't want that. So we set max-height of the itemExpand to the actual height of the container since unlike width, the auto value for height will be determined by its child elements. So now the height and width of the itemExpand becomes equal to that of the container which was the result we wanted.

Now what happen if we set explicit width and height for the parent container, let's say width: 400px and height: 600px. Remember again that there is no explicit width and height for the child li so it defaults to auto. In this case you can change the style of itemExpand to max-width: width and min-height: height. Here we are never letting the width to exceed 400px which will be caused by aspect-ratio: 1 since we are setting the min-height of the li to 600px. Now that will again change if the width of the container is greater than its height. That's all for the explanation!

import "./styles.css";
import { useState, useRef, useEffect } from "react";

export default function App() {
  const [expand, setExpand] = useState(null);
  const [height, setHeight] = useState(null);
  const [width, setWidth] = useState(null);
  
  // added for responsive
  const [heightGreater, setHeightGreater] = useState(true);


  // ref for container element
  const ref = useRef(null);

  const data = [1, 2, 3, 4, 5, 6];

  const handleExpand = (d) => {
    if (expand === d) {
      setExpand(null);
    } else {
      setExpand(d);
    }
  };

  const item = {
    aspectRatio: "1",
    width: "10rem",
    backgroundColor: "red",
    transition: "all .5s ease",
    maxHeight: "10rem",
    maxWidth: "10rem"
  };

  const itemExpand = {
    aspectRatio: "1",
    position: "absolute",
    backgroundColor: "green",
    transition: "all .5s ease",
    transformOrigin: "center",
    // added for responsive
    minWidth: heightGreater ? "auto" : width,
    maxHeight: heightGreater ? "auto" : height,
    minHeight: heightGreater ? height : "auto",
    maxWidth: heightGreater ? width : "auto",
  };

  // set height and width of child element to parent height and width
  useEffect(() => {
    setHeight(ref.current.offsetHeight);
    setWidth(ref.current.offsetWidth);
    
    // added for responsive
    if (height > width) {
      setHeightGreater(true);
    } else {
      setHeightGreater(false);
    }
  },[expand]);

  return (
    <div className="App">
      <ul className="container" ref={ref}>
        {data.map((d, i) => (
          <li key={i}
            style={expand === d ? itemExpand : item}
            onClick={() => handleExpand(d)}
          ></li>
        ))}
      </ul>
    </div>
  );
}
.App {
  font-family: sans-serif;
  text-align: center;
}

li {
  list-style: none;
}

/* set explicit height and width for the container*/
.container {
  border: 4px solid blue;
  padding: 0;
  width: auto;
  height: auto;
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  position: relative;
}

.expand {
  background-color: green;
}

  •  Tags:  
  • css
  • Related