character
الگوها و توکنهای آماده برای انیمیشن طبیعی کاراکتر، چهره، حرکت و ژست
این Skill برای متحرکسازی آواتارها، ماسکاتها، کاراکترهای بازی و شخصیتهای تصویرسازیشده، الگوهای استانداردی برای walk cycle، پلکزدن، تنفس، حالات چهره، lip sync و ژستها ارائه میدهد.
با استفاده از توکنهای زمانبندی و نمونههای CSS، JavaScript و Framer Motion میتوانید حرکتهایی منسجم و قابلکنترل بسازید و برای کاربران دارای حساسیت حرکتی، حالت prefers-reduced-motion را نیز پوشش دهید.
چه چیزی میگیرید
- توکنهای آماده برای سرعت راهرفتن، پلکزدن و تغییر حالت چهره
- نمونههای CSS و Framer Motion برای walk cycle، idle و ژستهایی مانند wave
- الگوی سادهی lip sync بر اساس phoneme و زمانبندی صوت
- حرکتهای طبیعی برای تنفس، تابخوردن بدن و هماهنگی اندامها
- راهنمای پیادهسازی reduced motion برای دسترسپذیری بهتر
برای چه کاری خوب است
- ساخت آواتارهای تعاملی، ماسکاتهای محصول و کاراکترهای onboarding
- انیمیشن کاراکترهای دوبعدی در وب، بازی یا رابطهای تعاملی
- پروژههایی که به حرکتهای آماده و قابل تنظیم با Framer Motion یا CSS نیاز دارند
برای چه کاری نیست
- ریگینگ کامل سهبعدی یا شبیهسازی فیزیکی پیشرفته
- تولید خودکار انیمیشن نهایی بدون آمادهبودن اجزای بصری کاراکتر
- lip sync دقیق مبتنی بر تحلیل صوت یا تولید phoneme با مدل گفتار
دربارهٔ این اسکیل
Character
Animation patterns for characters and figures.
Standards Reference
| Standard | Documentation |
|---|---|
| W3C DTCG | designtokens.org |
| Lottie Bodymovin | lottiefiles.com |
| Spine 2D | esotericsoftware.com |
| Rive | rive.app |
Quick Reference
Load from: tokens/character.tokens.json
Walk Cycle Timing
| Token | Value | Use |
|---|---|---|
walk-cycle.duration.slow | 1.5s | Leisurely |
walk-cycle.duration.normal | 1s | Average |
walk-cycle.duration.fast | 0.6s | Brisk |
walk-cycle.duration.run | 0.4s | Running |
Blink Timing
| Token | Value | Use |
|---|---|---|
blink.duration | 150ms | Close-open |
blink.interval-min | 2s | Min between |
blink.interval-max | 6s | Max between |
Expression Transitions
| Token | Value | Use |
|---|---|---|
expression.transition-quick | 200ms | Fast change |
expression.transition-normal | 400ms | Standard |
expression.transition-slow | 800ms | Dramatic |
Selection Guide
What character animation?
│
├── LOCOMOTION → Walk/run cycles
│ ├── Slow walk → duration.slow (1.5s cycle)
│ ├── Normal → duration.normal (1s cycle)
│ ├── Fast → duration.fast (0.6s cycle)
│ └── Run → duration.run (0.4s cycle)
│
├── FACIAL → Eyes and expression
│ ├── Blink → blink.duration (150ms)
│ ├── Quick expression → transition-quick
│ └── Dramatic → transition-slow
│
├── IDLE → Standing/waiting
│ ├── Breathing → idle.breathing-period (4s)
│ └── Sway → idle.sway-period (3s)
│
└── GESTURES → Actions
├── Wave → gesture.wave (600ms)
├── Nod → gesture.nod (400ms)
└── Point → gesture.point (300ms)
Patterns
Eye Blink
CSS:
@keyframes blink {
0%, 45%, 55%, 100% {
transform: scaleY(1);
}
50% {
transform: scaleY(0.1); /* Almost closed */
}
}
.eye {
transform-origin: center;
animation: blink 150ms ease-in-out; /* {motion.character.blink.duration} */
}
JavaScript (Natural blink interval):
class BlinkController {
constructor(eyeElement) {
this.eye = eyeElement;
this.minInterval = 2000; // {motion.character.blink.interval-min}
this.maxInterval = 6000; // {motion.character.blink.interval-max}
this.blinkDuration = 150; // {motion.character.blink.duration}
this.scheduleBlink();
}
blink() {
this.eye.style.transform = 'scaleY(0.1)';
setTimeout(() => {
this.eye.style.transform = 'scaleY(1)';
}, this.blinkDuration / 2);
this.scheduleBlink();
}
scheduleBlink() {
const interval = this.minInterval +
Math.random() * (this.maxInterval - this.minInterval);
setTimeout(() => this.blink(), interval);
}
}
Walk Cycle
CSS:
.character {
animation: walk 1s steps(8) infinite; /* {motion.character.walk-cycle.duration.normal} */
}
/* Sprite sheet approach */
@keyframes walk {
from { background-position: 0 0; }
to { background-position: -800px 0; } /* 8 frames x 100px */
}
/* Vertical bob */
@keyframes walkBob {
0%, 50%, 100% { transform: translateY(0); }
25% { transform: translateY(-5px); } /* {motion.character.walk-cycle.bob-amount.normal} */
75% { transform: translateY(-5px); }
}
.character-body {
animation: walkBob 1s ease-in-out infinite;
}
Framer Motion (Procedural):
const WalkCycle = ({ speed = 'normal' }) => {
const durations = {
slow: 1.5,
normal: 1,
fast: 0.6,
run: 0.4
};
const duration = durations[speed];
return (
<motion.div
animate={{
y: [0, -5, 0, -5, 0], // {motion.character.walk-cycle.bob-amount.normal}
}}
transition={{
duration,
repeat: Infinity,
times: [0, 0.25, 0.5, 0.75, 1],
ease: 'easeInOut'
}}
>
<motion.div
animate={{
rotate: [-15, 15, -15] // Arm swing
}}
transition={{
duration,
repeat: Infinity,
ease: 'easeInOut'
}}
style={{ transformOrigin: 'top' }}
>
{/* Arm */}
</motion.div>
</motion.div>
);
};
Idle Breathing
CSS:
@keyframes breathing {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.02); /* {motion.character.idle.breathing-amount} */
}
}
.character-body {
animation: breathing 4s ease-in-out infinite;
/* {motion.character.idle.breathing-period} */
transform-origin: bottom center;
}
Framer Motion:
const IdleCharacter = () => (
<motion.div
animate={{
scaleY: [1, 1.02, 1], // Breathing
rotate: [-2, 2, -2] // {motion.character.idle.sway-amount}
}}
transition={{
scaleY: {
duration: 4, // {motion.character.idle.breathing-period}
repeat: Infinity,
ease: 'easeInOut'
},
rotate: {
duration: 3, // {motion.character.idle.sway-period}
repeat: Infinity,
ease: 'easeInOut'
}
}}
style={{ transformOrigin: 'bottom center' }}
/>
);
Facial Expressions
Framer Motion:
const expressions = {
neutral: { eyebrowY: 0, mouthScale: 1, mouthY: 0 },
happy: { eyebrowY: -2, mouthScale: 1.2, mouthY: 2 },
sad: { eyebrowY: 5, mouthScale: 0.8, mouthY: -2 },
surprised: { eyebrowY: -8, mouthScale: 1.5, mouthY: 0 },
angry: { eyebrowY: 3, mouthScale: 0.9, mouthY: 0 }
};
const Face = ({ expression }) => {
const current = expressions[expression];
return (
<div className="face">
{/* Eyebrows */}
<motion.div
animate={{ y: current.eyebrowY }}
transition={{
duration: 0.4 // {motion.character.expression.transition-normal}
}}
className="eyebrows"
/>
{/* Mouth */}
<motion.div
animate={{
scaleX: current.mouthScale,
y: current.mouthY
}}
transition={{ duration: 0.4 }}
className="mouth"
/>
</div>
);
};
Simple Lip Sync
JavaScript:
const phonemes = {
'A': 'open',
'E': 'wide',
'I': 'narrow',
'O': 'round',
'U': 'pucker',
'M': 'closed',
'rest': 'neutral'
};
class LipSync {
constructor(mouthElement) {
this.mouth = mouthElement;
this.transitionTime = 50; // {motion.character.lip-sync.transition}
}
setPhoneme(phoneme) {
const shape = phonemes[phoneme] || phonemes.rest;
this.mouth.style.transition = `all ${this.transitionTime}ms ease-out`;
this.mouth.className = `mouth mouth-${shape}`;
}
// Animate to audio timestamps
animateToSpeech(phonemeTimeline) {
phonemeTimeline.forEach(({ phoneme, time }) => {
setTimeout(() => this.setPhoneme(phoneme), time);
});
}
}
Gesture: Wave
Framer Motion:
const WaveGesture = ({ trigger }) => {
const controls = useAnimationControls();
useEffect(() => {
if (trigger) {
controls.start({
rotate: [0, 20, -10, 20, -10, 0],
transition: {
duration: 0.6, // {motion.character.gesture.wave}
times: [0, 0.2, 0.4, 0.6, 0.8, 1]
}
});
}
}, [trigger]);
return (
<motion.div
animate={controls}
style={{ transformOrigin: 'bottom' }}
className="arm"
>
<div className="hand">👋</div>
</motion.div>
);
};
Reduced Motion
For prefers-reduced-motion: reduce:
| Full Motion | Reduced Alternative |
|---|---|
| Walk cycle | Static pose or simple slide |
| Eye blink | Disabled or very infrequent |
| Breathing | Static or very subtle |
| Gestures | Instant or disabled |
Implementation:
@media (prefers-reduced-motion: reduce) {
.character {
animation: none;
}
.eye,
.character-body {
animation: none;
}
}
Cross-References
- See
squash-stretch/for cartoony deformation - See
anticipation/for action wind-up - See
follow-through/for natural motion flow - See
secondary-action/for supporting animations - See
emotion-mapping/for expression selection - See
lottie/for character export
References
- No additional references needed for this skill