consequence

طراحی زنجیره‌های انیمیشنی پیوسته برای نمایش طبیعی علت و معلول در رابط کاربری

این Skill الگوهای زنجیره‌ای direct، ripple، cascade، echo، branch و convergent را برای هماهنگ‌کردن پیامدهای انیمیشنی یک تعامل طراحی می‌کند. زمان انتشار، تأخیر، کاهش شدت و ارتباط فضایی بین عناصر را مشخص می‌کند.

برای ساخت رابط‌هایی مفید است که در آن هر کلیک، تغییر وضعیت یا تکمیل مرحله، واکنش‌های بعدی را واضح و طبیعی ایجاد می‌کند. الگوها برای پیاده‌سازی با GSAP و Framer Motion ارائه می‌شوند و حالت prefers-reduced-motion را هم در نظر می‌گیرند.

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

  • انتخاب الگوی مناسب برای زنجیره‌های direct، ripple، cascade، echo، branch و convergent
  • تنظیم delay، propagation timing و شدت واکنش‌ها در هر مرحله
  • نمونه‌های پیاده‌سازی با GSAP و Framer Motion
  • قواعد توقف زنجیره، محدودیت عمق و کاهش شدت انیمیشن
  • جایگزین‌های مناسب برای حالت `prefers-reduced-motion`

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

  • طراحی واکنش‌های چندمرحله‌ای برای فرم‌ها، دکمه‌ها و تغییر وضعیت‌ها
  • ساخت تعاملات ripple و cascade در داشبوردها، لیست‌ها و شبکه‌های کارت
  • هماهنگ‌کردن چند انیمیشن موازی پس از یک رویداد کاربر
  • ایجاد رابط‌هایی که رابطه علت و معلول را به‌صورت بصری و قابل‌فهم نشان می‌دهند

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

  • انیمیشن‌های مستقل و بدون ارتباط علت و معلولی
  • ساخت روایت‌های کامل ویدئویی یا موشن‌گرافیک‌های طولانی
  • وقتی فقط به یک transition ساده و فوری نیاز دارید

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

Consequence

Design cause-effect relationships between animations. When one motion causes another, consequence patterns ensure the chain feels connected, meaningful, and physically plausible.

Standards Reference

StandardDocumentation
W3C DTCGdesigntokens.org
Material Design 3m3.material.io/styles/motion
Disney PrinciplesFollow-through, secondary action

Quick Reference

Load from: tokens/consequence.tokens.json

Chain Types

TypeDelayTargetsIntensityExample
direct0ms1100%button → modal
ripple30ms/stepmultiple-15%/stepcard → neighbors
cascade50ms/stepsequential85%submit → validate → success
echo200ms130%notification → reminder pulse
branch0msparallel70% eachsubmit → (loading + disable)
convergentwaitsmergemax of inputs(valid + verified) → enable

Selection Guide

How does the consequence relate to the cause?

├── Single, immediate result
│   └── DIRECT chain
│       └── e.g., click button → open modal
│
├── Spreading outward from source
│   └── RIPPLE chain
│       └── e.g., select card → nearby cards react
│
├── Sequential steps, each triggering next
│   └── CASCADE chain
│       └── e.g., submit → validate → transform → success
│
├── Delayed callback to original
│   └── ECHO chain
│       └── e.g., notification arrives → subtle pulse later
│
├── One cause, multiple parallel effects
│   └── BRANCH chain
│       └── e.g., submit → (show loading + disable button + scroll)
│
└── Multiple causes required for effect
    └── CONVERGENT chain
        └── e.g., (all fields valid + captcha passed) → enable submit

Chain Patterns

Direct Chain

Immediate, single-target consequence.

Use case: User action → immediate response

// Button click → Modal open
const directChain = {
  trigger: { element: '.btn', event: 'click' },
  consequence: {
    element: '.modal',
    type: 'direct',
    delay: 0,
    animation: {
      opacity: { from: 0, to: 1 },
      scale: { from: 0.9, to: 1 },
      duration: 300,
      easing: 'emphasized-decelerate'
    }
  }
};

// GSAP implementation
document.querySelector('.btn').addEventListener('click', () => {
  gsap.fromTo('.modal',
    { opacity: 0, scale: 0.9 },
    { opacity: 1, scale: 1, duration: 0.3, ease: 'power2.out' }
  );
});

Framer Motion:

const [isOpen, setIsOpen] = useState(false);

// Direct: button click → modal visibility
<motion.button onClick={() => setIsOpen(true)}>
  Open
</motion.button>

<AnimatePresence>
  {isOpen && (
    <motion.div
      initial={{ opacity: 0, scale: 0.9 }}
      animate={{ opacity: 1, scale: 1 }}
      exit={{ opacity: 0, scale: 0.9 }}
      transition={{ duration: 0.3 }}
    >
      Modal Content
    </motion.div>
  )}
</AnimatePresence>

Ripple Chain

Radiating outward from source point.

Use case: Selection affecting neighbors, touch feedback

// Card select → nearby cards react
function rippleChain(sourceElement, targets) {
  const sourceRect = sourceElement.getBoundingClientRect();
  const sourceCenter = {
    x: sourceRect.left + sourceRect.width / 2,
    y: sourceRect.top + sourceRect.height / 2
  };

  targets.forEach(target => {
    const targetRect = target.getBoundingClientRect();
    const distance = Math.hypot(
      targetRect.left + targetRect.width/2 - sourceCenter.x,
      targetRect.top + targetRect.height/2 - sourceCenter.y
    );

    const delay = distance * 0.001; // 30ms per 30px
    const intensity = Math.max(0.1, 1 - (distance * 0.0015)); // Decay

    gsap.to(target, {
      scale: 1 + (0.05 * intensity),
      opacity: 0.8 + (0.2 * intensity),
      duration: 0.2,
      delay: delay,
      ease: 'power2.out',
      yoyo: true,
      repeat: 1
    });
  });
}

GSAP with stagger:

gsap.from('.grid-item:not(.selected)', {
  scale: 0.98,
  opacity: 0.7,
  duration: 0.25,
  stagger: {
    each: 0.03,
    from: document.querySelector('.selected'),
    grid: 'auto'
  },
  ease: 'power2.out'
});

Cascade Chain

Sequential chain where each step triggers the next.

Use case: Form submission, multi-step processes

// Submit → validate → transform → success
const cascadeChain = gsap.timeline();

// Step 1: Button press feedback
cascadeChain.to('.submit-btn', {
  scale: 0.95,
  duration: 0.1,
  ease: 'power2.in'
});

// Step 2: Validation visual (50ms delay between steps)
cascadeChain.to('.form-fields', {
  borderColor: '#4CAF50',
  stagger: 0.05,
  duration: 0.2
}, '+=0.05');

// Step 3: Button transforms
cascadeChain.to('.submit-btn', {
  backgroundColor: '#4CAF50',
  width: 50,
  borderRadius: 25,
  duration: 0.3,
  ease: 'power2.inOut'
}, '+=0.05');

// Step 4: Success checkmark
cascadeChain.to('.checkmark', {
  scale: 1,
  opacity: 1,
  duration: 0.25,
  ease: 'back.out(2)'
}, '+=0.05');

Framer Motion with orchestration:

const cascadeVariants = {
  hidden: {},
  visible: {
    transition: {
      staggerChildren: 0.05,
      delayChildren: 0
    }
  }
};

const stepVariants = {
  hidden: { opacity: 0, y: 10 },
  visible: {
    opacity: 1,
    y: 0,
    transition: { duration: 0.2 }
  }
};

Echo Chain

Delayed repetition at reduced intensity.

Use case: Reminder pulses, attention maintenance

// Notification → echo pulse after 200ms
function echoChain(element) {
  // Primary animation
  gsap.fromTo(element,
    { scale: 0.8, opacity: 0 },
    { scale: 1, opacity: 1, duration: 0.3, ease: 'back.out(2)' }
  );

  // Echo at 30% intensity after 200ms
  gsap.to(element, {
    scale: 1.05,
    boxShadow: '0 0 20px rgba(33, 150, 243, 0.3)',
    duration: 0.4,
    delay: 0.2 + 0.3, // After primary + 200ms gap
    ease: 'power2.inOut',
    yoyo: true,
    repeat: 1
  });
}

Branch Chain

Single trigger, multiple parallel consequences.

Use case: Form submit, state changes with multiple effects

// Submit → (loading + disable inputs + scroll to top)
function branchChain() {
  const branches = [];

  // Branch 1: Show loading
  branches.push(
    gsap.to('.loading-spinner', {
      opacity: 1,
      rotation: 360,
      duration: 0.3
    })
  );

  // Branch 2: Disable inputs
  branches.push(
    gsap.to('.form-input', {
      opacity: 0.5,
      pointerEvents: 'none',
      duration: 0.2
    })
  );

  // Branch 3: Scroll to top
  branches.push(
    gsap.to(window, {
      scrollTo: 0,
      duration: 0.4
    })
  );

  // All branches execute in parallel
  return Promise.all(branches);
}

Framer Motion parallel:

const branchVariants = {
  idle: {},
  loading: {
    transition: { staggerChildren: 0 } // Parallel, no stagger
  }
};

const spinnerVariants = {
  idle: { opacity: 0 },
  loading: { opacity: 1, rotate: 360 }
};

const inputVariants = {
  idle: { opacity: 1 },
  loading: { opacity: 0.5 }
};

Convergent Chain

Multiple causes combine to trigger consequence.

Use case: Multi-condition validation, compound events

// (email valid + password valid + captcha passed) → enable submit
const conditions = {
  emailValid: false,
  passwordValid: false,
  captchaPassed: false
};

function checkConvergence() {
  const allMet = Object.values(conditions).every(Boolean);

  if (allMet) {
    gsap.to('.submit-btn', {
      opacity: 1,
      scale: 1,
      pointerEvents: 'auto',
      duration: 0.3,
      ease: 'power2.out'
    });
  }
}

// Each condition updates and checks
function setCondition(key, value) {
  conditions[key] = value;
  checkConvergence();
}

Propagation Timing

TimingValueUse
instant0msSame frame
linked16msNext frame, perceptually connected
followup100msClear follow-up
delayed300msBuilds anticipation
deferred600msSeparate moment

Intensity Profiles

ProfileMultiplierUse
preserve1.0Full intensity maintained
amplify1.3Consequence > cause
diminish0.6Consequence < cause
transformmappedIntensity maps to different property

Spatial Propagation

// Radial propagation
const radialConfig = {
  origin: 'center',
  spread: 'circular',
  delayPerUnit: 30 // ms per distance unit
};

// Calculate delay based on distance from origin
function getRadialDelay(element, origin) {
  const distance = getDistance(element, origin);
  return distance * radialConfig.delayPerUnit / 1000;
}

Termination Rules

  • Max depth: 5 steps maximum
  • Min intensity: Stop when < 0.1
  • Boundary stop: Stop at container edges

Reduced Motion

For prefers-reduced-motion: reduce:

Chain TypeReduced Alternative
rippleInstant state change
cascadeSingle combined transition
echoSkip echo
branchSimultaneous fade

Cross-References

  • See sequencing/ for stagger patterns
  • See narrative-arc/ for chain timing within story phases
  • See intent/ for consequence purpose
  • See follow-through/ for Disney principle connection

References

  • references/chain-patterns.md — Detailed chain configurations

دانلود ZIP