| name | bellog-animations |
| description | Provides Framer Motion animation patterns and best practices specific to the Bellog blog project. Triggers when implementing animated components or interactions. |
Bellog Animation Patterns
This skill provides the animation patterns and best practices used throughout the Bellog blog project.
Core Animation Principles
- Organic movement - Use easing curves, never linear
- Stagger for rhythm - Create visual flow with staggered animations
- Living elements - Ambient animations that breathe life
- Consistent timing - Follow project timing standards
Animation Timing Standards
// Interaction timings
const INTERACTION_FAST = 0.2; // Button press, hover start
const INTERACTION_NORMAL = 0.3; // Standard hover effects
const INTERACTION_SLOW = 0.5; // Modal open, drawer slide
// Transition timings
const TRANSITION_FAST = 0.3; // Quick state changes
const TRANSITION_NORMAL = 0.4; // Page transitions (standard)
const TRANSITION_SLOW = 0.6; // Heavy content transitions
// Ambient timings
const AMBIENT_SLOW = 3; // Slow blob movement
const AMBIENT_NORMAL = 4; // Standard blob rhythm
const AMBIENT_FAST = 5; // Faster ambient motion
Pattern 1: Stagger Children
Use for lists, grids, and groups of elements.
When to use: Animating multiple items that should appear in sequence
Example from Intro.tsx:
const container = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: {
staggerChildren: 0.1, // 100ms delay between each child
delayChildren: 0.2 // Start after 200ms
}
}
};
const item = {
hidden: { y: 20, opacity: 0 },
show: {
y: 0,
opacity: 1,
transition: {
duration: 0.5,
ease: "easeInOut"
}
}
};
// Usage
<motion.div
variants={container}
initial="hidden"
animate="show"
>
{items.map(item => (
<motion.div key={item.id} variants={item}>
{item.content}
</motion.div>
))}
</motion.div>
Pattern 2: Living Blob Animations
Use for decorative elements, background shapes, ambient animations.
When to use: Creating organic, perpetual movement
Example from Intro.tsx:
const blobVariants = {
initial: {
scale: 1,
x: 0,
y: 0
},
hover: {
scale: [1, 1.2, 0.9, 1.1, 1], // Keyframes
x: [0, 20, -10, 5, 0],
y: [0, -15, 10, -5, 0],
transition: {
duration: 4,
repeat: Infinity,
repeatType: "mirror",
ease: "easeInOut"
}
}
};
// Multiple blobs with different rhythms
const blob1 = { duration: 3, ... };
const blob2 = { duration: 4.5, ... };
const blob3 = { duration: 5.2, ... };
Best practices:
- Layer multiple blobs with different durations for organic feel
- Use
repeatType: "mirror"for smooth loops - Keep movements subtle (scale: 0.9-1.2 range)
- Use blur and opacity to create depth
Pattern 3: Page Transitions
Use for route changes, content swapping.
When to use: Navigating between pages or major content changes
Example from template.tsx:
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 20 }}
transition={{
ease: "easeInOut",
duration: 0.4
}}
>
{children}
</motion.div>
Variations:
// Fade only
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
// Slide from right
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
// Scale + fade
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
Pattern 4: Hover Interactions
Use for buttons, cards, clickable elements.
When to use: Adding interactivity to user-actionable elements
const cardVariants = {
initial: { scale: 1 },
hover: {
scale: 1.02,
transition: {
duration: 0.3,
ease: "easeInOut"
}
},
tap: {
scale: 0.98
}
};
<motion.div
variants={cardVariants}
initial="initial"
whileHover="hover"
whileTap="tap"
>
Common hover patterns:
- Cards: scale(1.02) + shadow increase
- Buttons: scale(1.05) + slight lift
- Icons: rotate or scale
- Links: underline expand
Pattern 5: Scroll-Based Animations
Use for parallax, fade-ins, progress indicators.
When to use: Animations triggered by scroll position
import { useScroll, useTransform } from "framer-motion";
const { scrollYProgress } = useScroll();
const opacity = useTransform(scrollYProgress, [0, 0.5], [0, 1]);
const y = useTransform(scrollYProgress, [0, 0.5], [50, 0]);
<motion.div style={{ opacity, y }}>
{/* Content */}
</motion.div>
Example: Progress bar
const { scrollYProgress } = useScroll();
<motion.div
style={{ scaleX: scrollYProgress }}
className="fixed top-0 left-0 right-0 h-1 bg-primary origin-left"
/>
Pattern 6: Enter/Exit Animations
Use with AnimatePresence for conditional rendering.
When to use: Elements that appear and disappear
import { AnimatePresence } from "framer-motion";
const variants = {
hidden: { opacity: 0, y: -10 },
visible: { opacity: 1, y: 0 },
exit: { opacity: 0, y: -10 }
};
<AnimatePresence>
{isVisible && (
<motion.div
variants={variants}
initial="hidden"
animate="visible"
exit="exit"
transition={{ duration: 0.3 }}
>
{content}
</motion.div>
)}
</AnimatePresence>
Easing Functions
Use these, not "linear":
// Bellog standard
ease: "easeInOut" // Default for most animations
// Other options
ease: "easeOut" // For entrances
ease: "easeIn" // For exits
ease: [0.43, 0.13, 0.23, 0.96] // Custom cubic-bezier
Animation Checklist
Before finalizing animations:
- Timing follows project standards (0.2-0.5s for interactions)
- Uses
variantspattern (not inline animation props) - Easing is
easeInOutor appropriate alternative - No linear easing
- Stagger delay appropriate for number of items
- Exit animations defined if using AnimatePresence
- Performance: No layout thrashing (avoid animating width/height)
- Accessibility: Respects
prefers-reduced-motionif critical
Performance Tips
Transform over top/left:
// ✅ Good (GPU accelerated) { x: 100 } { translateX: "100px" } { scale: 1.1 } // ❌ Bad (layout recalc) { left: 100 } { width: "100%" }Will-change hint:
style={{ willChange: "transform" }}Layout animations (use sparingly):
<motion.div layout>
Reduced Motion
Respect user preferences:
import { useReducedMotion } from "framer-motion";
const shouldReduceMotion = useReducedMotion();
const variants = {
hidden: { opacity: 0, y: shouldReduceMotion ? 0 : 20 },
visible: { opacity: 1, y: 0 }
};
Common Mistakes to Avoid
❌ Don't: Inline animation props
<motion.div animate={{ opacity: 1 }} initial={{ opacity: 0 }}>
✅ Do: Use variants
const variants = { hidden: { opacity: 0 }, visible: { opacity: 1 } };
<motion.div variants={variants} initial="hidden" animate="visible">
❌ Don't: Linear easing
transition={{ duration: 0.3, ease: "linear" }}
✅ Do: Use curves
transition={{ duration: 0.3, ease: "easeInOut" }}
❌ Don't: Animate width/height directly
animate={{ width: "100%" }}
✅ Do: Use scale or layout
animate={{ scaleX: 1 }}
// or
<motion.div layout>
Quick Reference
// Standard fade + slide in
{ initial: { opacity: 0, y: 20 }, animate: { opacity: 1, y: 0 } }
// Standard hover
{ whileHover: { scale: 1.02 }, transition: { duration: 0.3 } }
// Standard stagger
{
variants: {
container: { transition: { staggerChildren: 0.1 } },
item: { hidden: { y: 20, opacity: 0 }, show: { y: 0, opacity: 1 } }
}
}
// Standard exit
{ exit: { opacity: 0, y: -10 }, transition: { duration: 0.2 } }
Remember: Animations should enhance the experience, not distract from it. When in doubt, keep it subtle and fast.