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