Production-ready animations with Framer Motion including gestures, layout animations, and scroll effects
# Framer Motion Animations for Google Antigravity
Create stunning animations with Framer Motion using Google Antigravity's Gemini 3 engine. This guide covers motion components, gestures, layout animations, and scroll-triggered effects.
## Basic Motion Components
```typescript
// components/animated/FadeIn.tsx
'use client';
import { motion, type Variants } from 'framer-motion';
import { ReactNode } from 'react';
interface FadeInProps {
children: ReactNode;
delay?: number;
duration?: number;
direction?: 'up' | 'down' | 'left' | 'right';
className?: string;
}
const directions = {
up: { y: 40 },
down: { y: -40 },
left: { x: 40 },
right: { x: -40 },
};
export function FadeIn({
children,
delay = 0,
duration = 0.5,
direction = 'up',
className,
}: FadeInProps) {
const variants: Variants = {
hidden: {
opacity: 0,
...directions[direction],
},
visible: {
opacity: 1,
x: 0,
y: 0,
transition: {
duration,
delay,
ease: [0.25, 0.1, 0.25, 1],
},
},
};
return (
<motion.div
initial="hidden"
animate="visible"
variants={variants}
className={className}
>
{children}
</motion.div>
);
}
```
## Staggered List Animation
```typescript
// components/animated/StaggeredList.tsx
'use client';
import { motion, type Variants } from 'framer-motion';
import { ReactNode } from 'react';
interface StaggeredListProps {
children: ReactNode[];
staggerDelay?: number;
className?: string;
}
const containerVariants: Variants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
delayChildren: 0.1,
},
},
};
const itemVariants: Variants = {
hidden: {
opacity: 0,
y: 20,
scale: 0.95,
},
visible: {
opacity: 1,
y: 0,
scale: 1,
transition: {
type: 'spring',
stiffness: 300,
damping: 24,
},
},
};
export function StaggeredList({
children,
staggerDelay = 0.1,
className,
}: StaggeredListProps) {
return (
<motion.ul
className={className}
variants={{
...containerVariants,
visible: {
...containerVariants.visible,
transition: {
staggerChildren: staggerDelay,
delayChildren: 0.1,
},
},
}}
initial="hidden"
animate="visible"
>
{children.map((child, index) => (
<motion.li key={index} variants={itemVariants}>
{child}
</motion.li>
))}
</motion.ul>
);
}
```
## Layout Animations
```typescript
// components/animated/ExpandableCard.tsx
'use client';
import { motion, AnimatePresence } from 'framer-motion';
import { useState } from 'react';
interface ExpandableCardProps {
title: string;
preview: string;
content: string;
image?: string;
}
export function ExpandableCard({
title,
preview,
content,
image,
}: ExpandableCardProps) {
const [isExpanded, setIsExpanded] = useState(false);
return (
<>
<motion.div
layoutId={`card-${title}`}
onClick={() => setIsExpanded(true)}
className="cursor-pointer overflow-hidden rounded-xl bg-white p-4 shadow-lg"
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
{image && (
<motion.img
layoutId={`image-${title}`}
src={image}
alt={title}
className="mb-4 h-48 w-full rounded-lg object-cover"
/>
)}
<motion.h3 layoutId={`title-${title}`} className="text-xl font-bold">
{title}
</motion.h3>
<motion.p layoutId={`preview-${title}`} className="mt-2 text-gray-600">
{preview}
</motion.p>
</motion.div>
<AnimatePresence>
{isExpanded && (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 0.5 }}
exit={{ opacity: 0 }}
className="fixed inset-0 z-40 bg-black"
onClick={() => setIsExpanded(false)}
/>
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<motion.div
layoutId={`card-${title}`}
className="max-h-[90vh] w-full max-w-2xl overflow-auto rounded-xl bg-white p-6 shadow-2xl"
>
{image && (
<motion.img
layoutId={`image-${title}`}
src={image}
alt={title}
className="mb-6 h-64 w-full rounded-lg object-cover"
/>
)}
<motion.h3
layoutId={`title-${title}`}
className="text-2xl font-bold"
>
{title}
</motion.h3>
<motion.p
layoutId={`preview-${title}`}
className="mt-2 text-gray-600"
>
{preview}
</motion.p>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.2 }}
className="mt-4"
>
<p className="text-gray-700">{content}</p>
</motion.div>
<button
onClick={() => setIsExpanded(false)}
className="mt-6 rounded-lg bg-black px-4 py-2 text-white"
>
Close
</button>
</motion.div>
</div>
</>
)}
</AnimatePresence>
</>
);
}
```
## Scroll-Triggered Animations
```typescript
// components/animated/ScrollReveal.tsx
'use client';
import { motion, useInView, useAnimation, type Variants } from 'framer-motion';
import { useEffect, useRef, ReactNode } from 'react';
interface ScrollRevealProps {
children: ReactNode;
width?: 'fit-content' | '100%';
className?: string;
}
export function ScrollReveal({
children,
width = 'fit-content',
className,
}: ScrollRevealProps) {
const ref = useRef(null);
const isInView = useInView(ref, { once: true, margin: '-100px' });
const controls = useAnimation();
useEffect(() => {
if (isInView) {
controls.start('visible');
}
}, [isInView, controls]);
const variants: Variants = {
hidden: {
opacity: 0,
y: 75,
},
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.5,
ease: 'easeOut',
},
},
};
return (
<div ref={ref} style={{ width }} className={className}>
<motion.div initial="hidden" animate={controls} variants={variants}>
{children}
</motion.div>
</div>
);
}
// Parallax scroll effect
export function ParallaxSection({
children,
offset = 50,
}: {
children: ReactNode;
offset?: number;
}) {
const ref = useRef(null);
const { scrollYProgress } = useScroll({
target: ref,
offset: ['start end', 'end start'],
});
const y = useTransform(scrollYProgress, [0, 1], [offset, -offset]);
return (
<div ref={ref} className="relative overflow-hidden">
<motion.div style={{ y }}>{children}</motion.div>
</div>
);
}
```
## Gesture Interactions
```typescript
// components/animated/DraggableCard.tsx
'use client';
import { motion, useMotionValue, useTransform } from 'framer-motion';
export function DraggableCard({ children }: { children: React.ReactNode }) {
const x = useMotionValue(0);
const rotate = useTransform(x, [-200, 200], [-25, 25]);
const opacity = useTransform(x, [-200, -100, 0, 100, 200], [0, 1, 1, 1, 0]);
return (
<motion.div
style={{ x, rotate, opacity }}
drag="x"
dragConstraints={{ left: 0, right: 0 }}
dragElastic={0.7}
whileDrag={{ cursor: 'grabbing' }}
className="cursor-grab rounded-xl bg-white p-6 shadow-xl"
>
{children}
</motion.div>
);
}
```
## Best Practices
Google Antigravity's Gemini 3 engine recommends these Framer Motion patterns: Use layoutId for shared element transitions. Implement AnimatePresence for exit animations. Leverage useInView for scroll-triggered effects. Use motion values for performance-critical animations. Keep animations subtle and purposeful for better UX.This Framer Motion prompt is ideal for developers working on:
By using this prompt, you can save hours of manual coding and ensure best practices are followed from the start. It's particularly valuable for teams looking to maintain consistency across their framer motion implementations.
Yes! All prompts on Antigravity AI Directory are free to use for both personal and commercial projects. No attribution required, though it's always appreciated.
This prompt works excellently with Claude, ChatGPT, Cursor, GitHub Copilot, and other modern AI coding assistants. For best results, use models with large context windows.
You can modify the prompt by adding specific requirements, constraints, or preferences. For Framer Motion projects, consider mentioning your framework version, coding style, and any specific libraries you're using.