Learn more about Mintlify
Enter your email to receive updates about new features and product releases.
Combine multiple animation properties, create gesture-based interactions, and orchestrate complex animation sequences for sophisticated effects
const scaleAnimation = {
animate: { scale: [1, 1.5, 0.5, 1] },
transition: {
duration: 2,
repeat: Infinity,
repeatType: "loop" as const
}
}
<motion.div
animate={scaleAnimation.animate}
transition={scaleAnimation.transition}
/>
const rotateAnimation = {
animate: { rotate: [0, 180, -180, 0] },
transition: {
duration: 2,
repeat: Infinity,
repeatType: "loop" as const,
ease: "linear"
}
}
const fadeAnimation = {
animate: {
opacity: [1, 0.2, 1],
scale: [1, 0.9, 1]
},
transition: {
duration: 1.5,
repeat: Infinity,
repeatType: "reverse" as const,
ease: "easeInOut"
}
}
const bounceAnimation = {
animate: {
y: [0, -50, 0],
scale: [1, 0.9, 1]
},
transition: {
duration: 0.8,
repeat: Infinity,
repeatType: "reverse" as const,
ease: "easeOut"
}
}
const flipAnimation = {
animate: { rotateY: [0, 360] },
transition: {
duration: 1.5,
repeat: Infinity,
repeatType: "loop" as const,
ease: "easeInOut"
}
}
<motion.div
animate={flipAnimation.animate}
transition={flipAnimation.transition}
style={{
transformStyle: 'preserve-3d',
backfaceVisibility: 'visible'
}}
/>
const x = useMotionValue(0)
const y = useMotionValue(0)
const rotate = useTransform(x, [-100, 100], [-30, 30])
const scale = useTransform(y, [-100, 100], [0.5, 3])
<motion.div
drag
dragElastic={0.2}
dragConstraints={{
top: -100,
left: -100,
right: 100,
bottom: 100
}}
style={{ x, y, rotate, scale }}
whileDrag={{ boxShadow: "0 8px 32px rgba(0,0,0,0.2)" }}
/>
const [rotationSpeed, setRotationSpeed] = useState(2)
const [scaleAmount, setScaleAmount] = useState(1.2)
<motion.div
animate={{
scale: [1, scaleAmount, 1],
rotate: [0, 180, 0],
borderRadius: ["0%", "50%", "0%"]
}}
transition={{
duration: rotationSpeed,
ease: "easeInOut",
repeat: Infinity,
repeatDelay: 0.5
}}
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
/>
import { useScroll, useSpring } from "framer-motion"
const { scrollYProgress } = useScroll()
const scaleX = useSpring(scrollYProgress, {
stiffness: 100,
damping: 30,
restDelta: 0.001
})
<motion.div
style={{ scaleX }}
className="fixed top-0 left-0 right-0 h-1 bg-blue-500 origin-left"
/>
type AnimationType = 'scale' | 'rotate' | 'fade' | 'bounce' | 'flip'
const [activeAnimations, setActiveAnimations] = useState<AnimationType[]>([])
const animations: Record<AnimationType, AnimationConfig> = {
scale: {
animate: { scale: [1, 1.5, 0.5, 1] },
transition: {
duration: 2,
repeat: Infinity,
repeatType: "loop" as const
}
},
rotate: {
animate: { rotate: [0, 180, -180, 0] },
transition: {
duration: 2,
repeat: Infinity,
repeatType: "loop" as const,
ease: "linear"
}
},
fade: {
animate: { opacity: [1, 0.2, 1], scale: [1, 0.9, 1] },
transition: {
duration: 1.5,
repeat: Infinity,
repeatType: "reverse" as const,
ease: "easeInOut"
}
},
bounce: {
animate: { y: [0, -50, 0], scale: [1, 0.9, 1] },
transition: {
duration: 0.8,
repeat: Infinity,
repeatType: "reverse" as const,
ease: "easeOut"
}
},
flip: {
animate: { rotateY: [0, 360] },
transition: {
duration: 1.5,
repeat: Infinity,
repeatType: "loop" as const,
ease: "easeInOut"
}
}
}
const toggleAnimation = (type: AnimationType) => {
setActiveAnimations(prev =>
prev.includes(type)
? prev.filter(t => t !== type)
: [...prev, type]
)
}
<motion.div
initial={false}
animate={activeAnimations.includes(type) ? animations[type].animate : {}}
transition={animations[type].transition}
style={{
transformStyle: 'preserve-3d',
backfaceVisibility: 'visible'
}}
/>
const [progress, setProgress] = useState(0)
const [isPlaying, setIsPlaying] = useState(false)
// Calculate animation values based on progress
const translateY = -50 * Math.sin(progress * Math.PI)
const opacity = 0.5 + 0.5 * Math.sin(progress * Math.PI * 2)
const scale = 1 + 0.3 * Math.sin(progress * Math.PI)
<motion.div
animate={{
y: translateY,
scale: scale,
opacity: opacity
}}
/>
const [selectedSpeed, setSelectedSpeed] = useState<'slow' | 'normal' | 'frame-by-frame'>('normal')
const speeds = {
'slow': 0.25,
'normal': 1,
'frame-by-frame': 0
}
const advanceFrame = () => {
setProgress(prev => Math.min(prev + 0.05, 1))
}
const resetFrame = () => {
setProgress(0)
}
useEffect(() => {
if (isPlaying && selectedSpeed !== 'frame-by-frame') {
const startTime = Date.now()
const duration = 2000
const animate = () => {
const elapsed = (Date.now() - startTime) * speeds[selectedSpeed]
const newProgress = Math.min((elapsed % duration) / duration, 1)
setProgress(newProgress)
animationRef.current = requestAnimationFrame(animate)
}
animationRef.current = requestAnimationFrame(animate)
return () => {
if (animationRef.current) cancelAnimationFrame(animationRef.current)
}
}
}, [isPlaying, selectedSpeed])
// Good: Uses GPU-accelerated transform
transform: `translateY(${y}px) scale(${scale})`
// Avoid: Triggers layout recalculation
top: `${y}px`
width: `${width}px`
.animated-element {
will-change: transform, opacity;
}
const getAnimationDistance = () => {
if (!containerRef.current) return 50
const containerWidth = containerRef.current.clientWidth
return Math.min(containerWidth * 0.25, 100)
}
<motion.ul
variants={{
visible: {
transition: {
staggerChildren: 0.1
}
}
}}
initial="hidden"
animate="visible"
>
{items.map(item => (
<motion.li
key={item}
variants={{
hidden: { opacity: 0, x: -20 },
visible: { opacity: 1, x: 0 }
}}
/>
))}
</motion.ul>
<motion.div
animate={{
scale: [1, 1.2, 1],
rotate: [0, 90, 0],
borderRadius: ["0%", "50%", "0%"]
}}
transition={{
duration: 3,
times: [0, 0.5, 1],
ease: "easeInOut"
}}
/>
const generateKeyframes = (count: number) => {
return Array.from({ length: count }, (_, i) => ({
rotate: (360 / count) * i,
scale: 1 + Math.sin((i / count) * Math.PI) * 0.5
}))
}