Anime.js Project — Build a Complete Animation Application
In this tutorial, you'll learn to build a complete Anime.js project. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Build a complete Anime.js landing page that combines targets, parameters, timelines, easing, SVG morphing, scroll animations, and performance best practices into one polished production-ready project.
What You'll Learn
By the end of this tutorial, you'll have built a complete landing page with hero animation, scroll-triggered feature reveals, a morphing icon, a timeline-based product showcase, and an optimized animation pipeline.
Why It Matters
Individual animation techniques are tools. A complete project teaches you how to orchestrate them together: when to use a timeline vs individual animations, how to coordinate scroll and time-based animation, and how to structure code for maintainability.
Real-World Use
Doda Browser's marketing site follows this exact architecture. The hero section uses timeline animations, feature cards use scroll-triggered reveals, icons morph between states, and every animation is GPU-accelerated for smooth performance.
Where This Fits in Your Learning Path
flowchart LR
A["Performance Optimization"] --> B["**Anime.js Project**"]
B --> C["Production Anime Apps"]
style B fill:#f97316,stroke:#c2410c,color:#fff
style A fill:#e5e7eb,stroke:#9ca3af,color:#374151
style C fill:#22c55e,stroke:#16a34a,color:#fff
Project Structure
The landing page consists of four sections:
- Hero: timeline-based entrance animation
- Features: scroll-triggered staggered card reveals
- Showcase: auto-playing product rotation
- CTA: morphing icon and final animation
Step 1: Hero Animation Timeline
const heroTl = anime.timeline({
easing: 'easeOutExpo',
duration: 800
})
heroTl.add({
targets: '.hero-title',
translateY: [-60, 0],
opacity: [0, 1]
}).add({
targets: '.hero-subtitle',
translateY: [-30, 0],
opacity: [0, 1]
}, '-=400').add({
targets: '.hero-cta',
scale: [0.8, 1],
opacity: [0, 1]
}, '-=200')
Expected output: The title drops in, then the subtitle fades in overlapping, then the CTA button scales up.
Step 2: Feature Cards Scroll Reveal
const featureCards = document.querySelectorAll('.feature-card')
const featureObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
anime({
targets: entry.target,
translateY: [80, 0],
opacity: [0, 1],
duration: 600,
delay: Array.from(featureCards).indexOf(entry.target) * 200,
easing: 'easeOutQuad'
})
featureObserver.unobserve(entry.target)
}
})
}, { threshold: 0.2 })
featureCards.forEach(card => featureObserver.observe(card))
Expected output: As the user scrolls to the features section, each card slides up and fades in with a 200ms stagger.
Step 3: Morphing Icon Rotator
const iconStates = [
{ d: 'M20,20 L80,20 L80,80 L20,80 Z' }, // square
{ d: 'M50,10 C70,10 90,30 90,50 C90,70 70,90 50,90 C30,90 10,70 10,50 C10,30 30,10 50,10 Z' }, // circle
{ d: 'M50,5 L95,50 L50,95 L5,50 Z' }, // diamond
{ d: 'M20,20 L80,20 L50,80 Z' } // triangle
]
let iconIndex = 0
setInterval(() => {
iconIndex = (iconIndex + 1) % iconStates.length
anime({
targets: '#morph-icon path',
d: iconStates[iconIndex].d,
duration: 800,
easing: 'easeInOutCubic'
})
}, 2500)
Expected output: The icon morphs through square, circle, diamond, and triangle shapes in a continuous loop.
Step 4: Product Carousel Timeline
const products = document.querySelectorAll('.product-item')
let currentProduct = 0
function showProduct(index) {
const tl = anime.timeline({ duration: 500, easing: 'easeOutQuad' })
if (currentProduct >= 0) {
tl.add({
targets: products[currentProduct],
opacity: [1, 0],
scale: [1, 0.8]
})
}
tl.add({
targets: products[index],
opacity: [0, 1],
scale: [0.8, 1]
})
currentProduct = index
}
setInterval(() => {
const next = (currentProduct + 1) % products.length
showProduct(next)
}, 4000)
Expected output: Products fade and scale in/out in a continuous carousel rotation.
Step 5: CTA Final Animation
document.querySelector('.cta-button').addEventListener('mouseenter', function() {
anime({
targets: this,
scale: 1.08,
boxShadow: '0 8px 25px rgba(78, 205, 196, 0.4)',
duration: 300,
easing: 'easeOutQuad'
})
})
document.querySelector('.cta-button').addEventListener('mouseleave', function() {
anime({
targets: this,
scale: 1,
boxShadow: '0 4px 15px rgba(78, 205, 196, 0.2)',
duration: 300,
easing: 'easeOutQuad'
})
})
Expected output: The CTA button scales up with an enhanced shadow on hover, creating a polished interactive effect.
Complete Integration
All steps combined with performance best practices:
// Apply will-change to animated elements
document.querySelectorAll('.hero-title, .hero-subtitle, .feature-card, .product-item').forEach(el => {
el.style.willChange = 'transform, opacity'
})
// Hero animation
const heroTl = anime.timeline({ easing: 'easeOutExpo', duration: 800 })
heroTl.add({ targets: '.hero-title', translateY: [-60, 0], opacity: [0, 1] })
heroTl.add({ targets: '.hero-subtitle', translateY: [-30, 0], opacity: [0, 1] }, '-=400')
heroTl.add({ targets: '.hero-cta', scale: [0.8, 1], opacity: [0, 1] }, '-=200')
// Feature cards scroll reveal
const cards = document.querySelectorAll('.feature-card')
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
anime({ targets: entry.target, translateY: [80, 0], opacity: [0, 1], duration: 600, delay: Array.from(cards).indexOf(entry.target) * 200, easing: 'easeOutQuad' })
observer.unobserve(entry.target)
}
})
}, { threshold: 0.2 })
cards.forEach(card => observer.observe(card))
Common Mistakes
1. Running all animations on page load without staggering
Starting every animation at once causes a CPU spike. Use timeline delays or staggered start times.
2. Not cleaning up IntersectionObservers
Observers that aren't disconnected continue running. Always call unobserve after triggering.
3. Using too many morph paths with different point counts
Mismatched path points cause visible artifacts. Ensure all morph states have identical command structures.
4. Forgetting to test on mobile
Desktop-smooth animations may stutter on mobile. Use transform/opacity only and test on actual devices.
5. Not removing will-change after animations complete
will-change keeps GPU allocation active. Remove it in the complete callback.
Practice Questions
Why use a timeline for the hero section? To sequence the title, subtitle, and CTA animations with precise overlapping timing.
How does the IntersectionObserver trigger card animations? It watches each card and fires once when the card enters the viewport with 20% visibility.
What causes the morph icon rotation to loop? setInterval changes the icon state every 2.5 seconds, and anime morphs to the new path.
Why is will-change important for performance? It tells the browser to pre-allocate GPU resources for transform and opacity changes.
How does the product carousel ensure smooth transitions? It uses a timeline that fades out the current product while fading in the next product.
Challenge
Extend the project with a loading screen animation that transitions into the hero. The loading screen shows a morphing logo, and when complete, fades out while the hero timeline begins.
FAQ
What's Next
Congratulations on building a complete Anime.js landing page! Continue with related topics:
| Tutorial | What You'll Learn |
|---|---|
| Staggering and Synchronization | Advanced staggering patterns and synchronized effects |
| SVG and Motion Paths | Animating elements along SVG motion paths |
Related topics: JavaScript animation architecture, CSS vs JavaScript animation decision guide.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro