sequencing

الگوهای حرفه‌ای برای اجرای هماهنگ، آبشاری و مرحله‌ای انیمیشن‌های چندعنصری

این مهارت برای طراحی و پیاده‌سازی توالی‌های انیمیشنی در لیست‌ها، گریدها، رابط‌های کاربری و timelineهای چندمرحله‌ای استفاده می‌شود. الگوهای stagger، cascade، ripple، اجرای مرکزبه‌بیرون و ترتیب معکوس را با CSS، GSAP و Framer Motion ارائه می‌کند.

با کمک راهنمای انتخاب تأخیر و overlap، می‌توانید انیمیشن‌هایی روان و قابل‌کنترل بسازید، مدت اجرای لیست‌های بزرگ را محدود کنید و برای prefers-reduced-motion جایگزین مناسب داشته باشید.

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

  • الگوی stagger برای ورود و خروج آیتم‌های لیست
  • ترتیب‌دهی خطی، قطری، ردیفی، ستونی و شعاعی برای گریدها
  • ساخت timelineهای چندمرحله‌ای با overlap و هماهنگی دقیق
  • محاسبه تأخیر بر اساس تعداد آیتم‌ها و سقف زمان کل انیمیشن
  • راهنمای پیاده‌سازی نسخه کم‌حرکت برای دسترس‌پذیری

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

  • توسعه‌دهندگان رابط کاربری که برای لیست‌ها و گریدها انیمیشن ورود طراحی می‌کنند
  • طراحان محصولی که به choreography دقیق بین بخش‌های یک صفحه نیاز دارند
  • پروژه‌های مبتنی بر CSS، GSAP یا Framer Motion
  • ساخت wave، ripple و cascade در داشبوردها، landing pageها و کامپوننت‌های تعاملی

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

  • انیمیشن تک‌عنصری بدون نیاز به توالی یا هماهنگی
  • ساخت ویدیو یا موشن‌گرافیک مستقل از رابط کاربری
  • پروژه‌هایی که به فیزیک‌سازی پیچیده یا شبیه‌سازی ذرات نیاز دارند

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

Sequencing

Orchestration patterns for coordinated multi-element animations.

Standards Reference

StandardDocumentation
W3C DTCGdesigntokens.org
Material Design 3m3.material.io/styles/motion
Disney PrinciplesStaging, Follow-Through

Quick Reference

Load from: tokens/sequencing.tokens.json

Stagger Delays

TokenValueUse
micro20msNear-simultaneous wave
tiny30msFast cascade
small50msList items (M3 recommended)
medium80msReadable sequence
large120msDeliberate emphasis
dramatic200msPresentation reveal

Overlap Ratios

TokenValueEffect
none0Strictly sequential
slight0.2Gentle flow
half0.5Balanced cascade
heavy0.7Near-simultaneous

Selection Guide

How many elements?
│
├── 3-5 items → Use stagger.small (50ms)
│   └── Total duration: ~250-450ms
│
├── 6-10 items → Use stagger.tiny (30ms)
│   └── Total duration: ~400-600ms
│
└── 10+ items → Use stagger.micro (20ms)
    └── Cap total at 800ms max

What pattern?
│
├── LIST → Linear stagger, forward order
│
├── GRID → Row-first, column-first, or diagonal
│
├── RADIAL → Center-out or edges-in
│
└── RANDOM → Shuffle order for organic feel

Patterns

List Stagger Entrance

Sequential entrance for list items.

CSS (custom properties):

.list-item {
  opacity: 0;
  transform: translateY(16px);
  animation: slideIn 300ms ease-out forwards;
}

.list-item:nth-child(1) { animation-delay: 0ms; }
.list-item:nth-child(2) { animation-delay: 50ms; }
.list-item:nth-child(3) { animation-delay: 100ms; }
.list-item:nth-child(4) { animation-delay: 150ms; }
.list-item:nth-child(5) { animation-delay: 200ms; }

@keyframes slideIn {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Dynamic with CSS custom properties */
.list-item {
  animation-delay: calc(var(--index) * 50ms); /* {motion.sequencing.stagger.small} */
}

Framer Motion:

const containerVariants = {
  hidden: {},
  visible: {
    transition: {
      staggerChildren: 0.05, // 50ms {motion.sequencing.stagger.small}
      delayChildren: 0.1
    }
  }
};

const itemVariants = {
  hidden: { opacity: 0, y: 16 },
  visible: {
    opacity: 1,
    y: 0,
    transition: { duration: 0.3, ease: [0.05, 0.7, 0.1, 1] }
  }
};

<motion.ul variants={containerVariants} initial="hidden" animate="visible">
  {items.map((item, i) => (
    <motion.li key={i} variants={itemVariants}>
      {item}
    </motion.li>
  ))}
</motion.ul>

GSAP:

gsap.from('.list-item', {
  y: 16,
  opacity: 0,
  duration: 0.3,
  stagger: 0.05, // {motion.sequencing.stagger.small}
  ease: 'power2.out'
});

Grid Stagger (Diagonal)

Diagonal wave across grid elements.

GSAP:

const grid = document.querySelectorAll('.grid-item');
const columns = 4;

gsap.from(grid, {
  scale: 0,
  opacity: 0,
  duration: 0.4,
  stagger: {
    each: 0.05,
    grid: [Math.ceil(grid.length / columns), columns],
    from: 'start', // or 'center', 'end', 'edges'
    axis: null // null for diagonal, 'x' for rows, 'y' for columns
  },
  ease: 'back.out(1.7)'
});

Framer Motion:

const gridVariants = {
  hidden: {},
  visible: {
    transition: {
      staggerChildren: 0.03,
      staggerDirection: 1,
      delayChildren: 0.1
    }
  }
};

// Calculate diagonal delay
function getDiagonalDelay(row: number, col: number) {
  return (row + col) * 0.05;
}

<motion.div className="grid" variants={gridVariants}>
  {items.map((item, i) => {
    const row = Math.floor(i / columns);
    const col = i % columns;
    return (
      <motion.div
        key={i}
        initial={{ opacity: 0, scale: 0 }}
        animate={{ opacity: 1, scale: 1 }}
        transition={{ delay: getDiagonalDelay(row, col) }}
      />
    );
  })}
</motion.div>

Center-Out Ripple

Elements animate from center outward.

GSAP:

gsap.from('.grid-item', {
  scale: 0,
  opacity: 0,
  duration: 0.5,
  stagger: {
    each: 0.03,
    from: 'center',
    grid: 'auto',
    ease: 'power2.out'
  }
});

Custom Implementation:

function centerOutStagger(elements, container) {
  const containerRect = container.getBoundingClientRect();
  const centerX = containerRect.width / 2;
  const centerY = containerRect.height / 2;

  const sorted = [...elements].sort((a, b) => {
    const aRect = a.getBoundingClientRect();
    const bRect = b.getBoundingClientRect();

    const aDist = Math.hypot(
      aRect.left + aRect.width/2 - containerRect.left - centerX,
      aRect.top + aRect.height/2 - containerRect.top - centerY
    );
    const bDist = Math.hypot(
      bRect.left + bRect.width/2 - containerRect.left - centerX,
      bRect.top + bRect.height/2 - containerRect.top - centerY
    );

    return aDist - bDist;
  });

  return sorted;
}

Timeline Orchestration

Complex multi-step animation sequence.

GSAP Timeline:

const tl = gsap.timeline();

tl.from('.hero-title', {
  y: 50,
  opacity: 0,
  duration: 0.6,
  ease: 'power2.out'
})
.from('.hero-subtitle', {
  y: 30,
  opacity: 0,
  duration: 0.4,
  ease: 'power2.out'
}, '-=0.3') // overlap 0.3s
.from('.hero-cta', {
  scale: 0.8,
  opacity: 0,
  duration: 0.3,
  ease: 'back.out(1.7)'
}, '-=0.2')
.from('.hero-image', {
  x: 100,
  opacity: 0,
  duration: 0.6,
  ease: 'power2.out'
}, '-=0.4');

Framer Motion (AnimatePresence + stagger):

const sequence = [
  { id: 'title', delay: 0 },
  { id: 'subtitle', delay: 0.2 },
  { id: 'cta', delay: 0.4 },
  { id: 'image', delay: 0.3 }
];

function Hero() {
  return (
    <>
      <motion.h1
        initial={{ y: 50, opacity: 0 }}
        animate={{ y: 0, opacity: 1 }}
        transition={{ delay: 0, duration: 0.6 }}
      />
      <motion.p
        initial={{ y: 30, opacity: 0 }}
        animate={{ y: 0, opacity: 1 }}
        transition={{ delay: 0.2, duration: 0.4 }}
      />
      {/* ... */}
    </>
  );
}

Cascade with Overlap

Elements start before previous finishes.

GSAP with Overlap:

const duration = 0.4;
const overlap = 0.5; // {motion.sequencing.overlap.half}
const stagger = duration * (1 - overlap); // 0.2s

gsap.from('.item', {
  y: 20,
  opacity: 0,
  duration: duration,
  stagger: stagger,
  ease: 'power2.out'
});

Framer Motion:

const containerVariants = {
  visible: {
    transition: {
      staggerChildren: 0.08, // duration * (1 - overlap)
    }
  }
};

const itemVariants = {
  hidden: { y: 20, opacity: 0 },
  visible: {
    y: 0,
    opacity: 1,
    transition: { duration: 0.4 }
  }
};

Stagger with Item Count Limit

Cap total stagger duration for large lists.

function calculateStagger(itemCount, maxDuration = 800) {
  const baseStagger = 50; // ms
  const maxItems = maxDuration / baseStagger; // 16 items

  if (itemCount <= maxItems) {
    return baseStagger;
  }

  // Reduce stagger for large lists
  return maxDuration / itemCount;
}

// Usage
const items = document.querySelectorAll('.item');
const stagger = calculateStagger(items.length);

gsap.from(items, {
  opacity: 0,
  stagger: stagger / 1000 // Convert to seconds
});

Reverse Stagger (Exit)

Exit animation in reverse order.

GSAP:

// Entrance
gsap.from('.item', {
  opacity: 0,
  stagger: 0.05
});

// Exit (reverse)
gsap.to('.item', {
  opacity: 0,
  stagger: {
    each: 0.03,
    from: 'end' // Start from last item
  }
});

Framer Motion:

const exitVariants = {
  exit: {
    transition: {
      staggerChildren: 0.03,
      staggerDirection: -1 // Reverse order
    }
  }
};

Stagger Calculation Guidelines

ItemsStaggerTotal Duration
380ms~240ms
550ms~250ms
840ms~320ms
1230ms~360ms
2020ms~400ms
5015ms~750ms (cap here)

Formula for capped stagger:

const stagger = Math.min(baseStagger, maxDuration / itemCount);

Reduced Motion

For prefers-reduced-motion: reduce:

Full MotionReduced Alternative
Staggered entranceSimultaneous fade-in
Wave/ripple effectsInstant appearance
Complex timelinesSingle fade transition

Implementation:

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

gsap.from('.item', {
  opacity: 0,
  stagger: prefersReducedMotion ? 0 : 0.05,
  duration: prefersReducedMotion ? 0.15 : 0.3
});
// Framer Motion
const staggerAmount = prefersReducedMotion ? 0 : 0.05;

const containerVariants = {
  visible: {
    transition: {
      staggerChildren: staggerAmount
    }
  }
};

Cross-References

  • See entrance-patterns/ for staggered entrance animations
  • See exit-patterns/ for staggered exit animations
  • See scroll-animations/ for scroll-triggered sequences
  • See duration/ for individual animation timing
  • See easing/ for cascade timing curves
  • See motion-principles/ for staging and follow-through

References

  • references/stagger-algorithms.md — Stagger calculation deep dive

دانلود ZIP