exaggeration
شدت موشن را با توجه به برند، موقعیت و هدف تجربه کاربری تنظیم کنید.
این Skill اصل Exaggeration در انیمیشن را به تصمیمهای عملی برای طراحی موشن تبدیل میکند؛ از micro-interactionهای ظریف تا لحظههای نمایشی و جشنگونه. با استفاده از سطحهایی مانند subtle، moderate و dramatic، مقدار scale، distance و timing بهصورت هماهنگ تنظیم میشود.
برای ساخت انیمیشنهای قابلخواندن و منسجم در CSS، GSAP، Framer Motion و Remotion کاربرد دارد و شدت حرکت را با شخصیت برند، مرحله روایت و نوع تعامل تطبیق میدهد. همچنین برای prefers-reduced-motion و جلوگیری از اغراق بیشازحد راهکار ارائه میکند.
چه چیزی میگیرید
- انتخاب سطح مناسب اغراق برای micro-interaction، اعلان، onboarding و لحظههای قهرمانانه
- محاسبه و تنظیم هماهنگ scale، فاصله حرکت و مدتزمان انیمیشن
- الگوهای آماده برای CSS Custom Properties، GSAP، Framer Motion و Remotion
- تطبیق شدت موشن با شخصیت برند و مرحله روایت
- راهنمای جلوگیری از موشن یکنواخت، نمایشی بیشازحد و ناسازگار با دسترسیپذیری
برای چه کاری خوب است
- طراحان و توسعهدهندگانی که برای UI، محصول دیجیتال یا ویدیو به سیستم شدت موشن نیاز دارند
- پروژههایی با برندهای حرفهای، دوستانه، بازیمحور یا جسور که باید شدت انیمیشن متفاوتی داشته باشند
- ساخت state change، اعلان، onboarding، hero animation و celebration با بازخورد بصری واضح
برای چه کاری نیست
- شبیهسازیهای دقیق فیزیکی، رابطهای علمی یا visualizationهایی که به حرکت کاملاً واقعگرایانه نیاز دارند
- پروژههایی که فقط به یک انیمیشن آماده و بدون تصمیمگیری درباره شدت، زمینه یا برند نیاز دارند
- زمانی که انیمیشن باید بدون توجه به `prefers-reduced-motion` یا دسترسیپذیری اجرا شود
دربارهٔ این اسکیل
Exaggeration
Pushing motion beyond reality for emotional impact and visual clarity. Real-world motion is often too subtle to read on screen.
Core Principle
Exaggerate motion to communicate clearly. Perfect reality looks lifeless on screen; amplification creates life.
Real life: Small gesture -> Barely visible on screen
Animation: Exaggerated gesture -> Clear, readable, emotional
Token Reference
Load from: tokens/exaggeration.tokens.json
| Token | Multiplier | Scale Boost | Use Case |
|---|---|---|---|
subtle | 1.1x | 1.05x | Enterprise, professional |
moderate | 1.3x | 1.15x | Consumer apps |
expressive | 1.6x | 1.30x | Playful, gaming |
dramatic | 2.0x | 1.50x | Hero moments, celebrations |
cartoon | 2.5x | 2.00x | Kids, character animation |
Applying Exaggeration
Scale
// Base scale change: 10%
const baseScale = 1.1;
// With exaggeration multiplier
const exaggeratedScale = 1 + (baseScale - 1) * multiplier;
// subtle (1.1x): 1 + 0.1 * 1.1 = 1.11
// moderate (1.3x): 1 + 0.1 * 1.3 = 1.13
// dramatic (2.0x): 1 + 0.1 * 2.0 = 1.20
Distance
// Base movement: 20px
const baseDistance = 20;
// With exaggeration multiplier
const exaggeratedDistance = baseDistance * multiplier;
// subtle (1.1x): 22px
// moderate (1.3x): 26px
// dramatic (2.0x): 40px
Timing (optional stretch)
// Base duration: 300ms
const baseDuration = 300;
// With timing stretch (usually subtle)
const stretchedDuration = baseDuration * timingStretch;
// subtle (1.0x): 300ms (no change)
// moderate (1.1x): 330ms
// dramatic (1.3x): 390ms
Context Mapping
By Use Case
| Context | Level | Rationale |
|---|---|---|
| Micro-interactions | subtle | Frequent, shouldn't distract |
| State changes | moderate | Clear feedback without drama |
| Notifications | expressive | Needs attention |
| Celebrations | dramatic | Peak emotional moment |
| Errors | moderate | Noticeable, not alarming |
| Onboarding | expressive | Memorable first impression |
By Brand Attitude
| Brand Style | Level | Examples |
|---|---|---|
| Elegant/Luxury | subtle | Hermes, Rolex, Apple |
| Professional | subtle-moderate | Salesforce, Notion |
| Friendly | moderate | Mailchimp, Slack |
| Playful | expressive | Discord, Duolingo |
| Bold | dramatic | Nike, Spotify |
By Narrative Phase
| Phase | Level | Purpose |
|---|---|---|
| Introduction | moderate-expressive | Grab attention |
| Rising action | moderate | Build steadily |
| Climax | dramatic | Maximum impact |
| Resolution | subtle-moderate | Calm return |
Implementation Presets
CSS Custom Properties
:root {
/* Exaggeration levels */
--exaggeration-subtle: 1.1;
--exaggeration-moderate: 1.3;
--exaggeration-expressive: 1.6;
--exaggeration-dramatic: 2.0;
/* Current level (set via JS or class) */
--exaggeration: var(--exaggeration-moderate);
/* Derived values */
--scale-boost: calc(1 + (0.1 * var(--exaggeration)));
--distance-boost: calc(20px * var(--exaggeration));
}
/* Usage */
.card-hover {
transform: scale(var(--scale-boost)) translateY(calc(var(--distance-boost) * -1));
}
/* Brand variants */
.brand-elegant { --exaggeration: var(--exaggeration-subtle); }
.brand-playful { --exaggeration: var(--exaggeration-expressive); }
GSAP
import { gsap } from 'gsap';
import tokens from '../tokens/exaggeration.tokens.json';
// Exaggeration utility
function withExaggeration(baseValue, level = 'moderate', type = 'distance') {
const config = tokens.exaggeration[level].$value;
switch (type) {
case 'distance':
return baseValue * config.distanceBoost;
case 'scale':
return 1 + (baseValue - 1) * config.scaleBoost;
case 'duration':
return baseValue * config.timingStretch;
default:
return baseValue * config.multiplier;
}
}
// Example: Button press with configurable exaggeration
function buttonPress(button, level = 'moderate') {
const baseScale = 0.95;
const baseY = 2;
return gsap.to(button, {
scale: withExaggeration(baseScale, level, 'scale'),
y: withExaggeration(baseY, level, 'distance'),
duration: 0.1,
ease: 'power2.out'
});
}
// Success celebration with dramatic exaggeration
function celebrate(element) {
const level = 'dramatic';
return gsap.timeline()
.to(element, {
scale: withExaggeration(1.2, level, 'scale'),
duration: 0.2,
ease: 'back.out(2)'
})
.to(element, {
y: withExaggeration(-30, level, 'distance'),
duration: 0.3,
ease: 'power2.out'
})
.to(element, {
scale: 1,
y: 0,
duration: 0.4,
ease: 'elastic.out(1, 0.5)'
});
}
// Contextual exaggeration based on component
function animateComponent(component, action) {
const contextMap = {
'button': 'subtle',
'card': 'moderate',
'notification': 'expressive',
'hero': 'dramatic'
};
const level = contextMap[component.type] || 'moderate';
// Apply animation with appropriate level
}
Framer Motion
import { motion } from 'framer-motion';
import tokens from '../tokens/exaggeration.tokens.json';
// Hook for exaggeration values
const useExaggeration = (level: keyof typeof tokens.exaggeration) => {
const config = tokens.exaggeration[level].$value;
return {
scale: (base: number) => 1 + (base - 1) * config.scaleBoost,
distance: (base: number) => base * config.distanceBoost,
duration: (base: number) => base * config.timingStretch,
multiplier: config.multiplier
};
};
// Component with configurable exaggeration
interface AnimatedCardProps {
exaggeration?: 'subtle' | 'moderate' | 'expressive' | 'dramatic';
children: React.ReactNode;
}
export const AnimatedCard: React.FC<AnimatedCardProps> = ({
exaggeration = 'moderate',
children
}) => {
const ex = useExaggeration(exaggeration);
return (
<motion.div
whileHover={{
y: -ex.distance(10),
scale: ex.scale(1.02),
transition: { duration: ex.duration(0.2) }
}}
whileTap={{
scale: ex.scale(0.98)
}}
>
{children}
</motion.div>
);
};
// Celebration with maximum exaggeration
const celebrationVariants = {
initial: { scale: 0, rotate: -180 },
celebrate: (exLevel: string) => {
const ex = tokens.exaggeration[exLevel].$value;
return {
scale: [0, ex.scaleBoost, 1],
rotate: [-180, 10 * ex.multiplier, 0],
transition: {
duration: 0.6 * ex.timingStretch,
times: [0, 0.6, 1],
ease: 'backOut'
}
};
}
};
export const Celebration: React.FC = () => (
<motion.div
variants={celebrationVariants}
initial="initial"
animate="celebrate"
custom="dramatic"
>
Celebration!
</motion.div>
);
Remotion
import { useCurrentFrame, interpolate, spring } from 'remotion';
import tokens from '../tokens/exaggeration.tokens.json';
// Exaggeration utilities for video
export const useExaggeration = (level: 'subtle' | 'moderate' | 'expressive' | 'dramatic') => {
const config = tokens.exaggeration[level].$value;
return {
applyToScale: (base: number) => 1 + (base - 1) * config.scaleBoost,
applyToDistance: (base: number) => base * config.distanceBoost,
applyToDuration: (baseFrames: number) => Math.round(baseFrames * config.timingStretch)
};
};
// Example: Title reveal with exaggeration
export const TitleReveal: React.FC<{
text: string;
startFrame: number;
exaggeration?: 'subtle' | 'moderate' | 'expressive' | 'dramatic';
}> = ({ text, startFrame, exaggeration = 'moderate' }) => {
const frame = useCurrentFrame();
const ex = useExaggeration(exaggeration);
const baseScale = 1.5;
const baseY = 50;
const baseDuration = 20;
const progress = spring({
frame: frame - startFrame,
fps: 30,
config: { damping: 12, stiffness: 100 }
});
const scale = interpolate(
progress,
[0, 0.5, 1],
[ex.applyToScale(baseScale), ex.applyToScale(1.1), 1]
);
const y = interpolate(
progress,
[0, 1],
[ex.applyToDistance(baseY), 0]
);
const opacity = interpolate(
frame,
[startFrame, startFrame + 10],
[0, 1],
{ extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }
);
return (
<div
style={{
transform: `scale(${scale}) translateY(${y}px)`,
opacity
}}
>
{text}
</div>
);
};
// Hero moment with dramatic exaggeration
export const HeroMoment: React.FC<{
triggerFrame: number;
children: React.ReactNode;
}> = ({ triggerFrame, children }) => {
const frame = useCurrentFrame();
const ex = useExaggeration('dramatic');
// Dramatic entrance
const progress = interpolate(
frame,
[triggerFrame, triggerFrame + ex.applyToDuration(30)],
[0, 1],
{ extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }
);
const scale = interpolate(
progress,
[0, 0.6, 0.8, 1],
[0, ex.applyToScale(1.3), ex.applyToScale(0.95), 1]
);
const y = interpolate(
progress,
[0, 0.6, 1],
[ex.applyToDistance(100), ex.applyToDistance(-20), 0]
);
return (
<div style={{ transform: `scale(${scale}) translateY(${y}px)` }}>
{children}
</div>
);
};
Realistic vs Stylized Spectrum
| Style | Exaggeration | Physics | Use Case |
|---|---|---|---|
| Photorealistic | 1.0x | Accurate | Simulation, data viz |
| Natural | 1.1x | Enhanced | Professional UI |
| Stylized | 1.5x | Adjusted | Consumer apps |
| Cartoon | 2.5x+ | Impossible | Entertainment, kids |
Accessibility
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
// Reduce exaggeration for accessibility
const exaggerationLevel = prefersReducedMotion ? 'subtle' : determinedLevel;
// Or cap maximum exaggeration
const cappedLevel = prefersReducedMotion
? Math.min(level, tokens.exaggeration.subtle.$value.multiplier)
: level;
Common Mistakes
- Uniform exaggeration - Different contexts need different levels
- Always dramatic - Save drama for meaningful moments
- Ignoring brand - Exaggeration must match brand personality
- Inconsistent - Same actions should have same exaggeration
- Overshadowing content - Animation serves content, not vice versa
Related Skills
- anticipation - Exaggeration level affects anticipation wind-up intensity
- squash-stretch - Exaggeration determines deformation magnitude
- follow-through - Exaggeration affects overshoot and settle amplitude
- secondary-action - Primary/secondary intensity gap follows exaggeration
- arc-motion - Exaggeration affects arc height and trajectory drama