Here is my code (I am using Tailwind CSS):
This is the Projects
component that holds all the cards in a CSS grid:
export default function Projects() {
const projectObjArray = [
{
title: "Project 1",
description: "some description",
background: "#f00",
link: ""
},
{
title: "Project 2",
description: "some description",
background: "#f00",
link: ""
},
{
title: "Project 3",
description: "some description",
background: "#f00",
link: ""
}
];
const [expanded, setExpanded] = useState(undefined);
return (
<div>
<h1 className="text-6xl mb-5 md:text-left md:ml-10 pb-5">My Projects</h1>
<div className="grid grid-cols-1 md:grid-cols-3 gap-10">
{projectObjArray.map((el, index) => {
if (index !== expanded) {
return (
<ProjectTemplate
projectobj={el}
key={el.title}
index={index}
setexpanded={setExpanded}
/>
);
} else {
return (
<ProjectTemplateExpanded
projectobj={el}
key={el.title}
index={index}
setexpanded={setExpanded}
/>
);
}
})}
</div>
</div>
);
}
When a card is clicked, its index gets saved in the expanded
state.
Then, the ProjectTemplateExpanded
component will render at that position instead of the ProjectTemplate
.
Here are these components:
export default function ProjectTemplate(props) {
return (
<motion.div
initial={{ borderRadius: "100px" }}
animate={{ borderRadius: "50px" }}
className="w-full h-56 flex flex-col justify-center"
style={{ background: props.projectobj.background }}
onClick={() => {
props.setexpanded(props.index);
}}
layoutId={`Container${props.projectobj.title}`}
transition={{ duration: 2 }}
>
<motion.h1
layoutId={`Title${props.projectobj.title}`}
// layout
transition={{ duration: 2 }}
className="text-2xl"
>
{props.projectobj.title}
</motion.h1>
<motion.p
layoutId={`Description${props.projectobj.title}`}
// layout
transition={{ duration: 2 }}
className="text-1xl"
>
{props.projectobj.description}
</motion.p>
</motion.div>
);
}
export default function ProjectTemplateExpanded(props) {
return (
<>
<div className="h-56"></div>
<motion.div
className="w-full fixed top-0 left-0 right-0 bottom-0 z-50 flex flex-col justify-center items-center"
style={{ background: "rgba(0, 0, 0, 0.5)" }}
onClick={() => {
props.setexpanded(undefined);
}}
>
<motion.div
layoutId={`Container${props.projectobj.title}`}
transition={{ duration: 2 }}
initial={{ borderRadius: "50px" }}
animate={{ borderRadius: "100px" }}
style={{
background: props.projectobj.background,
width: "80vw",
height: "50vh"
}}
onClick={(e) => {
e.stopPropagation();
}}
className="flex flex-col justify-center"
>
<motion.h1
layoutId={`Title${props.projectobj.title}`}
// layout
transition={{ duration: 2 }}
className="text-2xl"
>
{props.projectobj.title}
</motion.h1>
<motion.p
layoutId={`Description${props.projectobj.title}`}
// layout
transition={{ duration: 2 }}
className="text-1xl"
>
{props.projectobj.description}
</motion.p>
</motion.div>
</motion.div>
</>
);
}
The Projects component is wrapped with an AnimateSharedLayout
.
export default function App() {
return (
<div className="App text-white pt-5 px-5">
<AnimateSharedLayout type="crossfade">
<Projects />
</AnimateSharedLayout>
</div>
);
}
The documentation states:
To correct distortion on immediate children, add layout to those too.
So I've tried adding the layout prop beside the layoutId on the h1 and the p tag, but that didn't change anything.
This blog post states that scale correction gets applied on any component that takes part in a shared element transition (that's the case here, because of the layoutId set on the children)
What am I missing?
The solution is to put items-center
on the container. Then, the width of the h1
and p
elements will get corrected during the animation.