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
| Standard | Documentation |
|---|---|
| W3C DTCG | designtokens.org |
| Material Design 3 | m3.material.io/styles/motion |
| Disney Principles | Follow-through, secondary action |
Quick Reference
Load from: tokens/consequence.tokens.json
Chain Types
| Type | Delay | Targets | Intensity | Example |
|---|---|---|---|---|
| direct | 0ms | 1 | 100% | button → modal |
| ripple | 30ms/step | multiple | -15%/step | card → neighbors |
| cascade | 50ms/step | sequential | 85% | submit → validate → success |
| echo | 200ms | 1 | 30% | notification → reminder pulse |
| branch | 0ms | parallel | 70% each | submit → (loading + disable) |
| convergent | waits | merge | max 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
| Timing | Value | Use |
|---|---|---|
instant | 0ms | Same frame |
linked | 16ms | Next frame, perceptually connected |
followup | 100ms | Clear follow-up |
delayed | 300ms | Builds anticipation |
deferred | 600ms | Separate moment |
Intensity Profiles
| Profile | Multiplier | Use |
|---|---|---|
preserve | 1.0 | Full intensity maintained |
amplify | 1.3 | Consequence > cause |
diminish | 0.6 | Consequence < cause |
transform | mapped | Intensity 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 Type | Reduced Alternative |
|---|---|
| ripple | Instant state change |
| cascade | Single combined transition |
| echo | Skip echo |
| branch | Simultaneous 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