Create a Stunning Animated Hero with React.js and Framer Motion
Bring your web designs to life with Framer Motion! In this blog post, weโll explore how to create a visually stunning animated hero section using React.js and Framer Motion. Learn how smooth transitions, interactive animations, and colorful designs can enhance your website's user experience. You can download the source code for free.. ๐
๐ฅ What Youโll Learn
๐ป Animated Hero Section
Weโll craft an engaging hero section with:
- Smooth transitions to captivate your audience.
- Interactive animations that respond to user interactions.
โจ Why Framer Motion?
If youโve ever wanted to:
- Animate elements in your React projects.
- Trigger animations on scroll, hover, or page load.
- Enhance your user experience with motion designโthen this is the perfect tutorial for you!
Framer Motion makes it easy to integrate powerful animations into your React components, turning static designs into dynamic experiences.
๐ Step-by-Step Guide
Step 1: Set Up Your React Project
Start with a React project. If you donโt already have one:
yarn create vite my-project
cd my-project
yarn add -D tailwindcss postcss autoprefixer
yarn tailwindcss init -p
Install Framer Motion
yarn add framer-motion
Setup for Tailwindcss
/** @type {import('tailwindcss').Config} */
export default {
mode: 'jit',
content: [
'./index.html',
'./src/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {
fontFamily: {
clash: ['Cabin Sketch"', 'sans-serif'],
},
fontSize: {
'10xl': '10rem',
'11xl': '12rem',
'12xl': '14rem',
'13xl': '16rem',
},
colors: {
orange: '#FFEFDA',
pineapple: '#E4FFC0',
},
},
},
plugins: [],
}
/* index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
html, body {
font-family: "Cabin Sketch", sans-serif;
}
Step 2: Create the AnimatedImage Component
Inside the src
folder, create a new file called Hero.tsx
:
import React from 'react';
import { motion, MotionValue } from 'framer-motion';
interface AnimationProps {
x?: MotionValue<number>;
y?: MotionValue<number>;
rotateX?: MotionValue<number>;
rotateY?: MotionValue<number>;
rotate?: MotionValue<number>;
rotateZ?: MotionValue<number>;
scale?: MotionValue<number>;
}
interface AnimatedImageProps {
src: string;
alt: string;
width: number;
height: number;
className: string;
animationProps: AnimationProps;
}
const AnimatedImage: React.FC<AnimatedImageProps> = ({
src,
alt,
width,
height,
className,
animationProps,
}) => {
return (
<motion.div style={{ perspective: 1000, ...animationProps }}>
<img src={src} alt={alt} width={width} height={height} className={className} />
</motion.div>
);
};
export default AnimatedImage;
Step 3: Create A container component for AnimatedImage
AnimateImageContainer.tsx
import React from 'react';
import AnimatedImage from './AnimatedImage';
import { useTransform } from 'framer-motion';
interface AnimatedImagesContainerProps {
currentFlavor: string;
flavors: { [key: string]: any };
mouseX: any;
mouseY: any;
}
const AnimatedImagesContainer: React.FC<AnimatedImagesContainerProps> = ({
currentFlavor,
flavors,
mouseX,
mouseY,
}) => {
return (
<>
{/* Can Image */}
<div className="absolute inset-0 flex items-center justify-center">
<AnimatedImage
src={flavors[currentFlavor].can}
alt={`Tarragon ${currentFlavor} Can`}
width={200}
height={300}
className="object-contain relative left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1"
animationProps={{
x: useTransform(mouseX, [0, 1], [-30, 30]),
y: useTransform(mouseY, [0, 1], [-30, 30]),
rotateY: useTransform(mouseX, [0, 1], [-15, 15]),
rotateX: useTransform(mouseY, [0, 1], [15, -15]),
rotateZ: useTransform(mouseX, [0, 1], [-10, 10]),
}}
/>
</div>
{/* Slice Image */}
<div className="relative top-1/3 left-1/2">
<AnimatedImage
src={flavors[currentFlavor].slice}
alt={`${currentFlavor} Slice`}
width={250}
height={250}
className="object-contain absolute top-1/4 right-3/4"
animationProps={{
x: useTransform(mouseX, [0, 1], [-50, 50]),
y: useTransform(mouseY, [0, 1], [-50, 50]),
rotate: useTransform(mouseX, [0, 1], [-25, 25]),
rotateZ: useTransform(mouseX, [0, 1], [-10, 10]),
scale: useTransform(mouseY, [0, 1], [0.9, 1.1]),
}}
/>
</div>
{/* Leaf Image */}
<div className="relative top-1/3 left-1/2">
<AnimatedImage
src={currentFlavor === 'pineapple' ? flavors['pineapple'].leafImage : flavors['orange'].leafImage}
alt="Leaf"
width={100}
height={100}
className="object-contain relative bottom-1/2 right-1/4"
animationProps={{
x: useTransform(mouseX, [0, 1], [40, -40]),
y: useTransform(mouseY, [0, 1], [-40, 40]),
rotate: useTransform(mouseX, [0, 1], [-5, 15]),
rotateY: useTransform(mouseX, [0, 1], [-10, 10]),
scale: useTransform(mouseY, [0, 1], [0.9, 1.1]),
}}
/>
</div>
{/* Additional Slice Image */}
<div className="relative top-1/2 left-3/4">
<AnimatedImage
src={currentFlavor === 'pineapple' ? flavors['pineapple'].sliceImage : flavors['orange'].sliceImage}
alt="Slice"
width={250}
height={250}
className="object-contain relative bottom-3/4 right-1/2"
animationProps={{
x: useTransform(mouseX, [0, 1], [40, -40]),
y: useTransform(mouseY, [0, 1], [-40, 40]),
rotateZ: useTransform(mouseX, [1, 0], [-1, 1]),
scale: useTransform(mouseY, [0, 1], [0.9, 1.1]),
}}
/>
</div>
</>
);
};
export default AnimatedImagesContainer;
Step 4: Integrate the all Hero Component
Import and use the Hero component in App.tsx
:
import React, { useState, useRef, useEffect } from 'react';
import { useMotionValue } from 'framer-motion';
import { motion } from 'framer-motion'; // Import motion from framer-motion
import Header from './components/Header';
import FlavorDisplay from './components/FlavorDetail';
import AnimatedImagesContainer from './components/AnimatedImagesContainer';
import SwitchFlavorButton from './components/Button';
import images from './assets';
// Define your types
type Flavor = 'orange' | 'pineapple';
interface FlavorDetails {
name: string;
color: string;
textColor: string;
can: string;
slice: string;
leafImage: string;
sliceImage: string;
}
interface Flavors {
[key: string]: FlavorDetails;
}
// Define flavors with additional images
const flavors: Flavors = {
orange: {
name: 'Orange',
color: '#FFEFDA',
textColor: '#FFD399',
can: images.orange.can,
slice: images.orange.slices[0],
leafImage: images.orange.leaf,
sliceImage: images.orange.slices[1],
},
pineapple: {
name: 'Pineapple',
color: '#E4FFC0',
textColor: '#B7EC73',
can: images.pineapple.can,
slice: images.pineapple.slices[0],
leafImage: images.pineapple.slices[2], // you can use leaf images as well
sliceImage: images.pineapple.slices[1],
},
};
const App: React.FC = () => {
const [currentFlavor, setCurrentFlavor] = useState<Flavor>('orange');
const containerRef = useRef<HTMLDivElement>(null);
const mouseX = useMotionValue(0);
const mouseY = useMotionValue(0);
const switchFlavor = () => {
setCurrentFlavor((prev) => (prev === 'orange' ? 'pineapple' : 'orange'));
};
useEffect(() => {
const handleMouseMove = (event: MouseEvent) => {
const container = containerRef.current;
if (container) {
const { left, top, width, height } = container.getBoundingClientRect();
const x = event.clientX - left;
const y = event.clientY - top;
mouseX.set(x / width);
mouseY.set(y / height);
}
};
window.addEventListener('mousemove', handleMouseMove);
return () => {
window.removeEventListener('mousemove', handleMouseMove);
};
}, [mouseX, mouseY]);
return (
<motion.div
ref={containerRef}
className="relative w-full h-screen overflow-hidden"
style={{ backgroundColor: flavors[currentFlavor].color }}
key={currentFlavor} // Keying by flavor for triggering re-mounting and animations
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }} // Animation on exit
transition={{ duration: 0.5 }} // Animation duration
>
<Header title="Tarragon" navItems={['Products', 'About', 'Insights']} />
<FlavorDisplay
flavorName={flavors[currentFlavor].name}
textColor={flavors[currentFlavor].textColor}
/>
<AnimatedImagesContainer
currentFlavor={currentFlavor}
flavors={flavors}
mouseX={mouseX}
mouseY={mouseY}
/>
<SwitchFlavorButton onClick={switchFlavor} />
</motion.div>
);
};
export default App;
๐ฅ Enhance the Experience
- Trigger animations on scroll with React Intersection Observer.
- Use Framer Motion Variants for reusable animation logic.
- Explore SVG animations to add unique visual elements.
๐ Try It Yourself
Free Download the full project here ๐ Free Download from Buymecoffee
Github ๐ Github
Inspired Figma Design ๐ Figma
๐ฌ Watch the Video
For a completed video on YouTube:
Framer Motion Animated Hero with React.js
๐ฅ About Framer Motion
Framer Motion is a powerful library for React animations. It provides:
- Declarative animations with React components.
- Smooth transitions and interactive effects.
- Simple yet highly customizable APIs.
๐ก Conclusion
Motion design transforms static websites into engaging experiences. With Framer Motion, you can easily create animations that enhance your React projects.
Weโd love to hear your feedback! Let us know in the comments how youโd use these animations in your own projects.
๐ Like, Share, and Follow
Stay tuned for more tutorials on React.js, web design, and animations. Donโt forget to subscribe to our channel and share this guide with fellow developers!
Top comments (0)