Skip to content

Anime.js Scroll Animations — Complete Guide with Examples

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you'll learn about Anime.js scroll animations. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Anime.js scroll animations trigger and control animation progress based on the browser's scroll position, creating parallax effects, scroll-triggered reveals, and progress-driven motion.

What You'll Learn

By the end of this tutorial, you'll use Anime's scroll detection, create parallax scrolling effects, animate elements when they enter the viewport, and synchronize animation progress with scroll position.

Why It Matters

Scroll animations make pages feel alive and interactive. They guide the user's attention, tell a visual story, and create a premium browsing experience. Properly implemented scroll animations increase engagement and time on page.

Real-World Use

Doda Browser's landing page uses Anime.js scroll animations for feature reveals. As the user scrolls, feature cards fade in and slide up, a progress bar fills, and background elements move at different speeds for a parallax depth effect.

Where This Fits in Your Learning Path

flowchart LR
    A["SVG & Morph"] --> B["**Scroll Animations**"]
    B --> C["Performance Optimization"]
    C --> D["Anime.js Project"]
    D --> E["Advanced Anime.js"]
    style B fill:#f97316,stroke:#c2410c,color:#fff
    style A fill:#e5e7eb,stroke:#9ca3af,color:#374151
    style E fill:#22c55e,stroke:#16a34a,color:#fff

Basic Scroll-Triggered Animation

Use the Intersection Observer API or a scroll event listener to trigger Anime animations.

const elements = document.querySelectorAll('.animate-on-scroll')

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      anime({
        targets: entry.target,
        translateY: [50, 0],
        opacity: [0, 1],
        duration: 800,
        easing: 'easeOutQuad'
      })
      observer.unobserve(entry.target)
    }
  })
})

elements.forEach(el => observer.observe(el))

Expected output: As each element scrolls into view, it slides up from 50px below and fades in. The animation plays once.

Parallax Scrolling

Create parallax effects by mapping scroll position to animation properties.

window.addEventListener('scroll', () => {
  const scrollY = window.scrollY

  anime({
    targets: '.parallax-bg',
    translateY: scrollY * 0.3,
    duration: 1,
    easing: 'linear'
  })

  anime({
    targets: '.parallax-fg',
    translateY: scrollY * 0.1,
    duration: 1,
    easing: 'linear'
  })
})

Expected output: The background moves slower than the foreground as the user scrolls, creating a depth illusion.

Progress-Based Animation

Link animation progress directly to scroll position for a reading progress indicator.

const progressBar = document.querySelector('.progress-bar')

window.addEventListener('scroll', () => {
  const scrollProgress = window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)

  anime({
    targets: progressBar,
    scaleX: scrollProgress,
    duration: 1,
    easing: 'linear'
  })
})

Expected output: A progress bar at the top of the page fills from left to right as the user scrolls down.

Staggered Scroll Reveals

Reveal multiple elements with staggered timing as they enter the viewport.

const cards = document.querySelectorAll('.card')

const observer = new IntersectionObserver((entries) => {
  const visibleCards = entries.filter(e => e.isIntersecting)

  visibleCards.forEach((entry, i) => {
    anime({
      targets: entry.target,
      translateY: [60, 0],
      opacity: [0, 1],
      delay: i * 150,
      duration: 600,
      easing: 'easeOutQuad'
    })
    observer.unobserve(entry.target)
  })
})

cards.forEach(card => observer.observe(card))

Expected output: Cards stagger into view as the user scrolls, each appearing 150ms after the previous one.

Scroll-Controlled Timeline

Use scroll position to control a timeline's progress, creating a story-driven scroll experience.

const tl = anime.timeline({
  duration: 1,
  autoplay: false
})

tl.add({ targets: '.scene-1', opacity: [0, 1], scale: [0.8, 1] })
tl.add({ targets: '.scene-2', opacity: [0, 1], translateX: [-100, 0] })
tl.add({ targets: '.scene-3', opacity: [0, 1], rotate: ['-10deg', '0deg'] })

window.addEventListener('scroll', () => {
  const progress = window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)
  tl.seek(tl.duration * progress)
})

Expected output: Scrolling down advances the timeline through three scenes. Each scene animates in order as the user scrolls.

Common Mistakes

1. Creating new animations on every scroll event

Creating anime() on every scroll event is expensive. Use a single persistent animation and update its progress, or use duration: 1 for instant application.

2. Not throttling scroll event handlers

Scroll events fire rapidly. Use requestAnimationFrame or a throttle to limit updates to once per frame.

3. Forgetting to unobserve elements after animation

Continuously observing animated elements wastes resources. Call observer.unobserve after the animation triggers.

4. Using scroll animations on mobile without testing

Mobile scroll behavior differs from desktop. Test touch scroll, momentum scrolling, and address bar hide/show effects.

5. Animating layout properties during scroll

Animating width, height, or top triggers layout recalculations. Stick to transforms and opacity for scroll-driven animations.

Practice Questions

  1. How do you detect when an element enters the viewport? Use the Intersection Observer API to watch for visibility changes.

  2. What is parallax scrolling? Different layers move at different speeds as the user scrolls, creating a 3D depth illusion.

  3. How do you link animation progress to scroll position? Calculate scroll percentage and use anime.seek() or duration: 1 with the progress value.

  4. Why should you throttle scroll event handlers? Scroll events fire at high frequency. Throttling prevents excessive animation creation and frame drops.

  5. What properties are safest to animate during scroll? Transforms (translate, scale, rotate) and opacity. Avoid layout-triggering properties.

Challenge

Build a story page with three scenes. Each scene triggers a unique animation (fade, slide, rotate) when it scrolls into view. The animations should only play once.

FAQ

Does Anime.js have built-in scroll tracking?

No. Anime focuses on animation. Combine it with Intersection Observer or manual scroll listeners for scroll-triggered animation.

Can I animate elements based on scroll speed?

Yes. Track scroll position over time and calculate velocity. Pass the velocity value to animation parameters.

How do I create a sticky scroll animation?

Use position: sticky CSS on the element and update anime animation progress based on the sticky element's position.

Do scroll animations work on mobile?

Yes. Use passive scroll listeners and touch events. Test on actual devices for smoothness.

How can I debug scroll animation performance?

Use the browser's Performance tab. Look for long frames, layout thrashing, and excessive JS execution in the scroll handler.


Mini Project

Build a scroll-driven story page. Three content sections animate in sequence as the user scrolls. A progress bar at the top shows reading progress. Background colors transition between sections.

const sections = document.querySelectorAll('.story-section')
const progressBar = document.querySelector('.scroll-progress')
const bgColors = ['#ff6b6b', '#4ecdc4', '#a29bfe', '#ffe66d']

function updateScroll() {
  const scrollY = window.scrollY
  const winHeight = window.innerHeight
  const docHeight = document.documentElement.scrollHeight
  const maxScroll = docHeight - winHeight
  const progress = Math.min(scrollY / maxScroll, 1)

  anime({
    targets: progressBar,
    scaleX: progress,
    duration: 1,
    easing: 'linear'
  })

  sections.forEach((section, i) => {
    const rect = section.getBoundingClientRect()
    const isVisible = rect.top < winHeight - 100 && rect.bottom > 100

    if (isVisible) {
      document.body.style.backgroundColor = bgColors[i]
      const entryProgress = 1 - (rect.bottom / winHeight)
      anime({
        targets: section.querySelector('.content'),
        translateY: [50 - entryProgress * 50, 0],
        opacity: [Math.min(entryProgress * 2, 1), 1],
        duration: 1
      })
    }
  })
}

window.addEventListener('scroll', updateScroll, { passive: true })
<style>.story-section { min-height: 100vh; display: flex; align-items: center; justify-content: center; } .content { opacity: 0; } .scroll-progress { position: fixed; top: 0; left: 0; width: 100%; height: 4px; background: #333; transform-origin: left; } .scroll-progress span { display: block; height: 100%; background: #4ecdc4; transform: scaleX(0); }</style>
<div class="scroll-progress"><span style="display:block;height:100%;background:#4ecdc4;transform:scaleX(0)"></span></div>
<section class="story-section"><div class="content"><h2>Chapter 1: Discovery</h2><p>Our journey begins at dawn.</p></div></section>
<section class="story-section"><div class="content"><h2>Chapter 2: Challenge</h2><p>Obstacles appear on the path.</p></div></section>
<section class="story-section"><div class="content"><h2>Chapter 3: Triumph</h2><p>Victory is achieved at last.</p></div></section>

What's Next

Optimize your animations for production:

Tutorial What You'll Learn
Performance Optimization Tips for smooth 60fps animations
Anime.js Project Build a complete production-ready animation

Related topics: Intersection Observer API, requestAnimationFrame.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro