Smooth Layout Transitions
Chapter 7
2 min read
To make your portfolio feel premium, we’ll use Framer Motion to animate the project cards as they filter. The magic happens with the layout prop, which automatically calculates the delta between positions.
ProjectGrid.tsx)Since animations require browser APIs, this must be a Client Component. We use AnimatePresence to handle cards being added or removed from the DOM.
"use client";
import { motion, AnimatePresence } from 'framer-motion';
import ProjectCard from './ProjectCard';
import styles from './ProjectGrid.module.scss';
export default function ProjectGrid({ projects }: { projects: any[] }) {
return (
<motion.div layout className={styles.grid}>
<AnimatePresence mode='popLayout'>
{projects.map((project) => (
<motion.div
key={project.slug}
layout
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
transition={{ duration: 0.3 }}
>
<ProjectCard {...project} />
</motion.div>
))}
</AnimatePresence>
</motion.div>
);
}Use your global mixins to ensure the grid is responsive.
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: $spacing-md;
margin-top: $spacing-lg;
@include tablet {
grid-template-columns: repeat(2, 1fr);
}
@include mobile {
grid-template-columns: 1fr;
gap: $spacing-sm;
}
}A standard card utilizing Next.js Image for performance.
import Image from 'next/image';
import Link from 'next/link';
import styles from './ProjectCard.module.scss';
export default function ProjectCard({ title, slug, image, tags }: any) {
return (
<Link href={`/portfolio/${slug}`} className={styles.card}>
<div className={styles.imageWrapper}>
<Image
src={image}
alt={title}
fill
sizes="(max-width: 768px) 100vw, 33vw"
/>
</div>
<div className={styles.content}>
<h3>{title}</h3>
<div className={styles.tags}>
{tags.map((tag: string) => (
<span key={tag}>{tag}</span>
))}
</div>
</div>
</Link>
);
}Why use popLayout?
In AnimatePresence, mode='popLayout' allows exiting elements to be "popped" out of the flex/grid flow immediately. This prevents the remaining cards from "jumping" to their new positions instantly, allowing for a smooth sliding transition.