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
| Standard | Documentation |
|---|---|
| W3C DTCG | designtokens.org |
| After Effects | Wiggle expression reference |
| Processing/p5.js | Perlin noise documentation |
Quick Reference
Load from: tokens/procedural.tokens.json
Wiggle Parameters
| Token | Value | Use |
|---|---|---|
frequency-slow | 1/s | Gentle wandering |
frequency-medium | 3/s | Natural jitter |
frequency-fast | 8/s | Nervous energy |
amplitude-subtle | ±3 | Slight movement |
amplitude-medium | ±10 | Clear jitter |
amplitude-strong | ±25 | Pronounced shake |
Noise Parameters
| Token | Value | Use |
|---|---|---|
frequency-fine | 0.01 | Slow, smooth variation |
frequency-medium | 0.05 | Balanced variation |
octaves-simple | 1 | Clean noise |
octaves-complex | 6 | Organic 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 Motion | Reduced Alternative |
|---|---|
| Wiggle | Static or very subtle |
| Shake | No shake, or single position change |
| Noise motion | Static |
| Boil/hand-drawn | Static |
| Turbulence | Static |
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