scroll-animations

ساخت انیمیشن‌های روان و دسترس‌پذیر مبتنی بر اسکرول برای رابط‌های وب

این Skill برای پیاده‌سازی انیمیشن‌های scroll-triggered و scroll-linked در رابط‌های وب استفاده می‌شود؛ از reveal و stagger در زمان ورود به viewport تا parallax، progress indicator و scrub با موقعیت اسکرول. الگوهای آماده برای GSAP ScrollTrigger، Intersection Observer و CSS scroll-driven animations ارائه می‌کند.

با رعایت توکن‌های حرکتی M3، بهینه‌سازی عملکرد و پشتیبانی از prefers-reduced-motion، تجربه‌ای روان، قابل‌کنترل و دسترس‌پذیر می‌سازد. برای پروژه‌های Vanilla JS، React و SPA نیز الگوهای cleanup و مدیریت lifecycle دارد.

چه چیزی می‌گیرید

  • الگوهای reveal، stagger و trigger با Intersection Observer و GSAP ScrollTrigger
  • پیاده‌سازی parallax، progress indicator و انیمیشن‌های scrub‌شونده
  • مدیریت انیمیشن‌های بالای fold برای بهبود perceived performance
  • راهکارهای دسترس‌پذیری با `prefers-reduced-motion` و حذف حرکات پرخطر
  • نکات بهینه‌سازی، استفاده محدود از `will-change` و cleanup در React/SPA

برای چه کاری خوب است

  • ساخت landing pageها و وب‌سایت‌های محصول با reveal و parallax کنترل‌شده
  • پیاده‌سازی progress indicator یا scrub animation وابسته به اسکرول
  • توسعه رابط‌های React و SPA که به مدیریت lifecycle انیمیشن نیاز دارند
  • طراحی تجربه‌های تعاملی دسترس‌پذیر با پشتیبانی از reduced motion

برای چه کاری نیست

  • انیمیشن‌های مستقل از اسکرول یا طراحی کامل سیستم موشن بدون تعامل اسکرولی
  • پروژه‌هایی که به horizontal scroll hijack، zoom شدید یا حرکات 3D بدون کنترل نیاز دارند
  • صفحه‌هایی که انیمیشن اسکرولی در آن‌ها ارزش کاربردی ندارد یا عملکرد اولویت مطلق است

دربارهٔ این اسکیل

Scroll Animations

Scroll-triggered and scroll-linked motion patterns.

Standards Reference

Tool/APIDocumentation
GSAP ScrollTriggergsap.com/docs/v3/Plugins/ScrollTrigger
Intersection Observerdeveloper.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
CSS scroll-drivendeveloper.mozilla.org/en-US/docs/Web/CSS/CSS_scroll-driven_animations

Two Types of Scroll Animation

TypeBehaviorUse Case
Scroll-triggeredAnimation plays when element enters viewportReveal animations, stagger
Scroll-linkedAnimation progress tied to scroll positionParallax, scrubbing, progress

Scroll-Triggered (Reveal on Scroll)

Intersection Observer (Vanilla JS)

const observer = new IntersectionObserver(
  (entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        entry.target.classList.add('visible');
        observer.unobserve(entry.target); // Once only
      }
    });
  },
  { threshold: 0.2, rootMargin: '0px 0px -50px 0px' }
);

document.querySelectorAll('.animate-on-scroll').forEach(el => {
  observer.observe(el);
});
.animate-on-scroll {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 250ms cubic-bezier(0.05, 0.7, 0.1, 1),
              transform 250ms cubic-bezier(0.05, 0.7, 0.1, 1);
}

.animate-on-scroll.visible {
  opacity: 1;
  transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
  .animate-on-scroll {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

GSAP ScrollTrigger (Triggered)

import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';

gsap.registerPlugin(ScrollTrigger);

gsap.from('.card', {
  opacity: 0,
  y: 30,
  duration: 0.25, // medium-1
  ease: 'cubic-bezier(0.05, 0.7, 0.1, 1)', // emphasized-decelerate
  stagger: 0.05,
  scrollTrigger: {
    trigger: '.card-grid',
    start: 'top 80%',  // When top of trigger hits 80% of viewport
    toggleActions: 'play none none none'
  }
});

Scroll-Linked (Scrubbing)

GSAP ScrollTrigger (Scrub)

gsap.to('.progress-bar', {
  scaleX: 1,
  ease: 'none', // Linear for scroll-linked
  scrollTrigger: {
    trigger: 'body',
    start: 'top top',
    end: 'bottom bottom',
    scrub: true  // Links animation to scroll position
  }
});

Parallax

gsap.to('.parallax-bg', {
  y: -100,
  ease: 'none',
  scrollTrigger: {
    trigger: '.hero',
    start: 'top top',
    end: 'bottom top',
    scrub: true
  }
});

Viewport-Aware Stagger

Prioritize above-the-fold content for faster perceived load.

const cards = gsap.utils.toArray('.card');

// Determine which cards are above the fold
const foldPosition = window.innerHeight;
const aboveFold = cards.filter(card =>
  card.getBoundingClientRect().top < foldPosition
);
const belowFold = cards.filter(card =>
  card.getBoundingClientRect().top >= foldPosition
);

// Phase 1: Above fold — faster, tighter stagger
gsap.from(aboveFold, {
  opacity: 0,
  y: 16,
  duration: 0.2,  // short-4
  ease: 'cubic-bezier(0.05, 0.7, 0.1, 1)',
  stagger: 0.04   // Tighter
});

// Phase 2: Below fold — triggered on scroll
gsap.from(belowFold, {
  opacity: 0,
  y: 16,
  duration: 0.25, // medium-1
  ease: 'cubic-bezier(0.05, 0.7, 0.1, 1)',
  stagger: 0.05,
  scrollTrigger: {
    trigger: belowFold[0],
    start: 'top 85%'
  }
});

Reduced Motion

Critical: Scroll animations often trigger vestibular issues.

const prefersReduced = window.matchMedia(
  '(prefers-reduced-motion: reduce)'
).matches;

if (prefersReduced) {
  // Option 1: Disable all scroll animations
  ScrollTrigger.getAll().forEach(st => st.kill());
  gsap.set('.animate', { opacity: 1, y: 0 });

  // Option 2: Keep reveals, remove parallax
  // Reveals are OK (fade only), parallax is not
}

Safe vs. Unsafe Scroll Animations

Safe (Reduced Motion OK)Unsafe (Disable)
Fade on scrollParallax
Progress indicatorHorizontal scroll hijack
Simple revealZoom on scroll
Sticky headers3D transforms

Performance

  1. Use will-change sparingly
  2. Animate transforms/opacity only — avoid layout properties
  3. Use scrub: true not scrub: 0.5 for smoother scrubbing
  4. Batch observers — one observer for multiple elements
  5. Kill ScrollTriggers on unmount (React/SPA)
// React cleanup
useEffect(() => {
  const ctx = gsap.context(() => {
    // ScrollTrigger animations here
  });
  return () => ctx.revert(); // Cleanup
}, []);

See references/scrolltrigger-patterns.md for advanced GSAP patterns.
See references/intersection-observer.md for vanilla JS patterns.
See references/scrubbing-media.md for Lottie/video scrubbing.
See references/parallax.md for parallax with accessibility.

دانلود ZIP