procedural

به انیمیشن‌های خشک، با Wiggle، Noise و Shake حس طبیعی و ارگانیک بدهید.

این Skill الگوهای موشن procedural برای ساخت Wiggle، Shake، Drift، Turbulence و حرکت مبتنی بر Perlin Noise ارائه می‌کند. می‌توانید با پارامترهای مشخص، لرزش دوربین، تنوع تصادفی، حرکت شناور و حس دست‌کشیده ایجاد کنید.

برای پروژه‌های UI، موشن گرافیک و انیمیشن‌های تعاملی مفید است؛ چون شدت، فرکانس، دامنه، decay و جزئیات noise را کنترل می‌کند و راهکارهای JavaScript، CSS و GSAP را همراه با ملاحظات prefers-reduced-motion نشان می‌دهد.

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

  • تنظیمات آماده برای frequency، amplitude، noise و octaves
  • الگوهای JavaScript و CSS برای Wiggle، Shake، Drift و Boil
  • نمونه‌های GSAP برای حرکت تصادفی و لرزش کاهشی
  • حرکت‌های ارگانیک با Perlin Noise، Turbulence و Spring physics
  • راهنمای سازگارکردن انیمیشن‌ها با `prefers-reduced-motion`

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

  • ساخت انیمیشن‌های UI که بیش از حد مکانیکی و دقیق به نظر می‌رسند
  • ایجاد لرزش دوربین و واکنش‌های Impact در بازی‌ها و موشن گرافیک
  • طراحی سبک دست‌کشیده، خط‌جوش یا حرکت شناور برای عناصر تعاملی
  • پروژه‌های JavaScript، CSS یا GSAP که به تغییرات تصادفی کنترل‌شده نیاز دارند

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

  • انیمیشن‌های کاملاً قطعی و قابل‌پیش‌بینی که نباید هیچ تغییر تصادفی داشته باشند
  • موشن‌های ساده و خطی که با transition یا keyframe معمولی حل می‌شوند
  • پروژه‌هایی که حرکت مداوم آن‌ها بدون پشتیبانی از کاهش حرکت، تجربه کاربری را مختل می‌کند

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

Procedural

Generative and noise-based animation patterns.

Standards Reference

StandardDocumentation
W3C DTCGdesigntokens.org
After EffectsWiggle expression reference
Processing/p5.jsPerlin noise documentation

Quick Reference

Load from: tokens/procedural.tokens.json

Wiggle Parameters

TokenValueUse
frequency-slow1/sGentle wandering
frequency-medium3/sNatural jitter
frequency-fast8/sNervous energy
amplitude-subtle±3Slight movement
amplitude-medium±10Clear jitter
amplitude-strong±25Pronounced shake

Noise Parameters

TokenValueUse
frequency-fine0.01Slow, smooth variation
frequency-medium0.05Balanced variation
octaves-simple1Clean noise
octaves-complex6Organic detail

Selection Guide

What effect?
│
├── WIGGLE → Random continuous motion
│   ├── Gentle → freq: 1, amp: 3
│   ├── Natural → freq: 3, amp: 10
│   └── Nervous → freq: 8, amp: 25
│
├── SHAKE → Impact/reaction effect
│   ├── Subtle → intensity-subtle
│   ├── Strong → intensity-strong
│   └── With decay → exponential falloff
│
├── NOISE → Smooth random variation
│   ├── Simple → 1 octave
│   ├── Organic → 3-6 octaves
│   └── Property: position, rotation, scale, etc.
│
├── ORGANIC FEEL → Hand-drawn imperfection
│   ├── Subtle → 2% imperfection
│   ├── Sketchy → 10% imperfection
│   └── Line boil → frame-by-frame variation
│
└── DRIFT → Wandering motion
    ├── Slow → speed 0.5, bounds 50
    └── Active → speed 2, bounds 100

Patterns

Wiggle Effect

After Effects-style wiggle motion.

JavaScript Implementation:

function wiggle(frequency, amplitude, time) {
  // Simplified wiggle - for production use noise library
  const value = Math.sin(time * frequency * Math.PI * 2) *
                Math.cos(time * frequency * 1.3 * Math.PI * 2) *
                amplitude;
  return value;
}

function applyWiggle(element, freqX, ampX, freqY, ampY) {
  let startTime = performance.now();

  function animate() {
    const time = (performance.now() - startTime) / 1000;

    const x = wiggle(freqX, ampX, time);
    const y = wiggle(freqY, ampY, time);

    element.style.transform = `translate(${x}px, ${y}px)`;
    requestAnimationFrame(animate);
  }

  animate();
}

// Usage: natural jitter
applyWiggle(element, 3, 10, 3, 10);
// {motion.procedural.wiggle.frequency-medium}, {motion.procedural.wiggle.amplitude-medium}

GSAP:

// GSAP wiggle using random
function gsapWiggle(element, amplitude, frequency) {
  const duration = 1 / frequency;

  gsap.to(element, {
    x: `random(-${amplitude}, ${amplitude})`,
    y: `random(-${amplitude}, ${amplitude})`,
    duration: duration,
    repeat: -1,
    repeatRefresh: true, // New random each repeat
    ease: "sine.inOut"
  });
}

Perlin Noise Motion

Smooth, organic random motion.

JavaScript (simplex-noise library):

import { createNoise2D } from 'simplex-noise';

const noise2D = createNoise2D();

function noiseMotion(element, config) {
  const {
    amplitude = 30,    // {motion.procedural.noise.amplitude-strong}
    frequency = 0.05,  // {motion.procedural.noise.frequency-medium}
    speed = 1
  } = config;

  let time = 0;

  function animate() {
    time += 0.016 * speed; // ~60fps

    const x = noise2D(time * frequency, 0) * amplitude;
    const y = noise2D(0, time * frequency) * amplitude;
    const rotation = noise2D(time * frequency * 0.5, time * frequency * 0.5) * 5;

    element.style.transform = `translate(${x}px, ${y}px) rotate(${rotation}deg)`;
    requestAnimationFrame(animate);
  }

  animate();
}

Shake Effect

Impact shake with decay.

CSS:

@keyframes shake {
  0%, 100% { transform: translate(0, 0) rotate(0); }
  10% { transform: translate(-10px, -5px) rotate(-2deg); }
  20% { transform: translate(8px, 4px) rotate(1deg); }
  30% { transform: translate(-6px, -3px) rotate(-1deg); }
  40% { transform: translate(4px, 2px) rotate(0.5deg); }
  50% { transform: translate(-2px, -1px) rotate(-0.5deg); }
  60% { transform: translate(1px, 0.5px) rotate(0); }
}

.shake {
  animation: shake 0.5s ease-out;
}

JavaScript with Decay:

function shake(element, intensity, duration, decay = 'exponential') {
  const { x, y, rotation } = intensity;
  // intensity = {motion.procedural.shake.intensity-medium}
  const startTime = performance.now();

  function animate() {
    const elapsed = performance.now() - startTime;
    const progress = elapsed / (duration * 1000);

    if (progress >= 1) {
      element.style.transform = '';
      return;
    }

    // Calculate decay
    let decayFactor;
    if (decay === 'exponential') {
      decayFactor = Math.pow(1 - progress, 2);
    } else if (decay === 'linear') {
      decayFactor = 1 - progress;
    } else {
      decayFactor = 1;
    }

    // Random offset with decay
    const offsetX = (Math.random() - 0.5) * 2 * x * decayFactor;
    const offsetY = (Math.random() - 0.5) * 2 * y * decayFactor;
    const rot = (Math.random() - 0.5) * 2 * rotation * decayFactor;

    element.style.transform = `translate(${offsetX}px, ${offsetY}px) rotate(${rot}deg)`;
    requestAnimationFrame(animate);
  }

  animate();
}

// Usage: medium shake for 0.5s
shake(element, { x: 5, y: 5, rotation: 1 }, 0.5);

GSAP:

function gsapShake(element, intensity, duration) {
  const tl = gsap.timeline();

  const shakes = 10;
  const shakeDuration = duration / shakes;

  for (let i = 0; i < shakes; i++) {
    const decay = 1 - (i / shakes);
    const x = (Math.random() - 0.5) * intensity.x * decay;
    const y = (Math.random() - 0.5) * intensity.y * decay;
    const r = (Math.random() - 0.5) * intensity.rotation * decay;

    tl.to(element, {
      x, y, rotation: r,
      duration: shakeDuration,
      ease: "power2.out"
    });
  }

  tl.to(element, { x: 0, y: 0, rotation: 0, duration: shakeDuration });

  return tl;
}

Hand-Drawn / Boil Effect

Frame-by-frame organic variation.

CSS (multiple keyframe states):

@keyframes boil {
  0% { transform: translate(0, 0) rotate(0deg); }
  25% { transform: translate(0.5px, -0.5px) rotate(0.2deg); }
  50% { transform: translate(-0.5px, 0.5px) rotate(-0.2deg); }
  75% { transform: translate(0.3px, 0.3px) rotate(0.1deg); }
  100% { transform: translate(0, 0) rotate(0deg); }
}

.hand-drawn {
  animation: boil 0.083s steps(1) infinite; /* 12fps */
}

JavaScript:

function lineBoil(element, amplitude = 1, fps = 12) {
  const interval = 1000 / fps;

  setInterval(() => {
    const x = (Math.random() - 0.5) * amplitude * 2;
    const y = (Math.random() - 0.5) * amplitude * 2;
    const r = (Math.random() - 0.5) * amplitude;

    element.style.transform = `translate(${x}px, ${y}px) rotate(${r}deg)`;
  }, interval);
}

Turbulence Effect

Chaotic, swirling motion.

JavaScript:

function turbulence(element, config) {
  const {
    intensity = 0.3, // {motion.procedural.turbulence.intensity-moderate}
    scale = 50,      // {motion.procedural.turbulence.scale-medium}
    speed = 1
  } = config;

  const noise = createNoise2D();
  let time = 0;

  function animate() {
    time += 0.016 * speed;

    // Multiple noise layers for turbulence
    const x = noise(time * 0.5, 0) * scale * intensity +
              noise(time * 1.5, 100) * scale * 0.5 * intensity;
    const y = noise(0, time * 0.5) * scale * intensity +
              noise(100, time * 1.5) * scale * 0.5 * intensity;
    const rotation = noise(time, time) * 10 * intensity;

    element.style.transform = `translate(${x}px, ${y}px) rotate(${rotation}deg)`;
    requestAnimationFrame(animate);
  }

  animate();
}

Drift / Wander

Slow, bounded random movement.

JavaScript:

function drift(element, config) {
  const {
    bounds = 50,        // {motion.procedural.drift.bounds-medium}
    speed = 1,          // {motion.procedural.drift.speed-medium}
    returnForce = 0.01  // {motion.procedural.drift.return-force}
  } = config;

  let x = 0, y = 0;
  let vx = 0, vy = 0;

  function animate() {
    // Random acceleration
    vx += (Math.random() - 0.5) * speed * 0.1;
    vy += (Math.random() - 0.5) * speed * 0.1;

    // Return force toward origin
    vx -= x * returnForce;
    vy -= y * returnForce;

    // Damping
    vx *= 0.98;
    vy *= 0.98;

    // Update position
    x += vx;
    y += vy;

    // Soft bounds
    if (Math.abs(x) > bounds) x *= 0.9;
    if (Math.abs(y) > bounds) y *= 0.9;

    element.style.transform = `translate(${x}px, ${y}px)`;
    requestAnimationFrame(animate);
  }

  animate();
}

Spring-Based Procedural

Physics spring following noise target.

JavaScript:

function springyNoise(element, config) {
  const {
    tension = 150,    // {motion.procedural.spring.tension-normal}
    friction = 15,    // {motion.procedural.spring.friction-medium}
    noiseScale = 50
  } = config;

  const noise = createNoise2D();
  let time = 0;

  // Current values
  let x = 0, y = 0;
  let vx = 0, vy = 0;

  function animate() {
    time += 0.016;

    // Target from noise
    const targetX = noise(time * 0.3, 0) * noiseScale;
    const targetY = noise(0, time * 0.3) * noiseScale;

    // Spring physics
    const ax = (targetX - x) * tension / 1000 - vx * friction / 100;
    const ay = (targetY - y) * tension / 1000 - vy * friction / 100;

    vx += ax;
    vy += ay;
    x += vx;
    y += vy;

    element.style.transform = `translate(${x}px, ${y}px)`;
    requestAnimationFrame(animate);
  }

  animate();
}

Combining Procedural Effects

Layered Noise

Multiple properties with different noise.

function layeredProcedural(element) {
  const noise = createNoise2D();
  let time = 0;

  function animate() {
    time += 0.016;

    // Position: slow, large amplitude
    const x = noise(time * 0.2, 0) * 30;
    const y = noise(0, time * 0.2) * 30;

    // Rotation: medium speed, small amplitude
    const rotation = noise(time * 0.5, 100) * 5;

    // Scale: fast, tiny amplitude
    const scale = 1 + noise(time * 2, 200) * 0.02;

    element.style.transform = `translate(${x}px, ${y}px) rotate(${rotation}deg) scale(${scale})`;
    requestAnimationFrame(animate);
  }

  animate();
}

Reduced Motion

For prefers-reduced-motion: reduce:

Full MotionReduced Alternative
WiggleStatic or very subtle
ShakeNo shake, or single position change
Noise motionStatic
Boil/hand-drawnStatic
TurbulenceStatic

Implementation:

const prefersReducedMotion = window.matchMedia(
  '(prefers-reduced-motion: reduce)'
).matches;

if (prefersReducedMotion) {
  // Skip procedural animations entirely
  return;
}

// Or significantly reduce intensity
const intensity = prefersReducedMotion ? 0.1 : 1;

Cross-References

  • See looping/ for continuous procedural motion
  • See position/ for drift/float effects
  • See rotation/ for wobble patterns
  • See shake/ in this file for impact reactions

References

  • references/noise-functions.md — Noise algorithm implementations

دانلود ZIP