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

StandardDocumentation
W3C DTCGdesigntokens.org
Lottie Bodymovinlottiefiles.com
Spine 2Desotericsoftware.com
Riverive.app

Quick Reference

Load from: tokens/character.tokens.json

Walk Cycle Timing

TokenValueUse
walk-cycle.duration.slow1.5sLeisurely
walk-cycle.duration.normal1sAverage
walk-cycle.duration.fast0.6sBrisk
walk-cycle.duration.run0.4sRunning

Blink Timing

TokenValueUse
blink.duration150msClose-open
blink.interval-min2sMin between
blink.interval-max6sMax between

Expression Transitions

TokenValueUse
expression.transition-quick200msFast change
expression.transition-normal400msStandard
expression.transition-slow800msDramatic

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 MotionReduced Alternative
Walk cycleStatic pose or simple slide
Eye blinkDisabled or very infrequent
BreathingStatic or very subtle
GesturesInstant 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

دانلود ZIP