✦ Pixellمستندات

کهکشان ذره‌ای: چطور کار می‌کند؟

این انیمیشن در CodeBeats تابعی ۳۷ خطی به نام fxGalaxy است. چند صد نقطه، اول یک دایره‌ی آفتابگردانی می‌سازند. وقتی ماوس روی آن می‌رود (یا AI مشغول کار است)، از مرکز به بیرون به یک کهکشان مارپیچی چرخان تبدیل می‌شوند. این صفحه قدم‌به‌قدم ریاضی و کد آن را باز می‌کند. هر جا دمو هست، با اسلایدرهایش بازی کنید.

۱. کد اصلی کجاست
۲. فایل‌های پوشه‌ی Pixell
۳. ایده‌ی کلی در یک نگاه
۴. حلقه‌ی انیمیشن
۵. شکل A: دایره‌ی آفتابگردانی
۶. شکل B: کهکشان مارپیچی
۷. تبدیل A به B (mix و m)
۸. سرعت و چرخش
۹. واکنش به ماوس
۱۰. رسم: نرمی، چشمک، هاله، نور
۱۱. کارایی
۱۲. جدول همه‌ی پارامترها
۱۳. استفاده در صفحه‌ی خودتان (API)
۱۴. ساختن شکل جدید
۱۵. تمرین‌ها

۱. کد اصلی کجاست

فایل در CodeBeatsچه چیزی
static/fx.js خطوط ۱۹ تا ۵۵خود تابع window.fxGalaxy(canvas, options) و پالت PAL
static/fx.js خط ۸reduce: آیا کاربر در سیستم‌عامل «کاهش حرکت» را روشن کرده
templates/index.html خط ۲۹ و ۶۳۱بوم ۱۵۰ پیکسلی در کادر Develop: fxGalaxy(cv, {host: $("dv_hero")})
static/ai.js خط ۱۱۳ و ۱۱۴صفحه‌ی خوش‌آمد پنل AI: fxGalaxy(cv, {n: 260})
static/ds.css خطوط ۳۲۸ تا ۳۳۹استایل کادر .hero و اندازه‌ی ۱۵۰×۱۵۰ بوم

یک کپی دست‌نخورده از همین خطوط در original/ گذاشته شده تا کنار این مستندات بخوانید. هیچ چیزی در CodeBeats تغییر نکرده است.

۲. فایل‌های پوشه‌ی Pixell

فایلکار
galaxy.jsموتور. همان الگوریتم اصلی، با این تفاوت که هر عدد ثابت یک گزینه شده و شکل‌های هندسی و چهره‌های خطی داخلی هم دارد. با تنظیمات پیش‌فرض دقیقاً همان خروجی CodeBeats را می‌دهد.
index.html + playground.jsآزمایشگاه: صحنه‌ی بزرگ و پنل تنظیمات با پیش‌تنظیم‌ها، خروجی PNG/JSON/کد و لینک اشتراک
docs.htmlهمین مستندات، با دموهای زنده
example.htmlکوتاه‌ترین کد استفاده و بازسازی کادر Develop
original/کپی کلمه‌به‌کلمه‌ی کد و CSS اصلی، فقط برای مطالعه
style.cssظاهر صفحه‌های Pixell (نه خود انیمیشن)
serve.cmdاجرای یک وب‌سرور محلی روی پورت 8080

۳. ایده‌ی کلی در یک نگاه

هر نقطه دو «جای هدف» دارد: جایش در شکل A و جایش در شکل B. هر فریم این مراحل اجرا می‌شود:

زمان: dt← هدف تبدیل: want (ماوس / AI)← mix آرام به سمت want می‌رود← سرعت spd و زاویه‌ی rot← برای هر نقطه: جای A و جای B← ترکیب با m← هل ماوس← حرکت نرم و رسم
نکته‌ی کلیدی: هیچ نقطه‌ای مستقیم جابه‌جا نمی‌شود. همه چیز با درون‌یابی (lerp) کار می‌کند: a + (b - a) * t. وقتی t صفر است جواب a است، وقتی یک است b، و بین این دو یک نقطه‌ی میانی.

۴. حلقه‌ی انیمیشن

// fx.js خط ۳۲-۳۶
function frame(now){ if(!cv.isConnected) return; requestAnimationFrame(frame);
  const dt = Math.min(50, now-last) / 16.67; last = now;
  if(!vis || document.hidden) return;
  const dpr = Math.min(2, devicePixelRatio||1), cw = cv.clientWidth, ch = cv.clientHeight;
  if(cw!=w || ch!=h){ w=cw; h=ch; cv.width=w*dpr; cv.height=h*dpr }
  ctx.setTransform(dpr,0,0,dpr,0,0); ctx.clearRect(0,0,w,h);

۵. شکل A: دایره‌ی آفتابگردانی (Phyllotaxis)

// fx.js خط ۲۴ و ۴۵
orR = Math.sqrt((i + .5) / N)      // شعاع
orA = i * 2.39996                  // زاویه = i × زاویه‌ی طلایی
ox = Math.cos(orA + rot) * orR;  oy = Math.sin(orA + rot) * orR;

این همان الگوی چیدمان دانه‌های گل آفتابگردان است. دو ترفند دارد:

زاویه را از ۱۳۷٫۵ کمی تغییر دهید تا پره‌ها ظاهر شوند. «توان شعاع» ۰٫۵ همان جذر است.

۶. شکل B: کهکشان مارپیچی

// fx.js خط ۲۳-۲۴: یک بار، هنگام ساخت ذره
gr = Math.pow(Math.random(), .65)                          // فاصله از مرکز، 0..1
ga = (i % ARMS) * 2π / ARMS                                // کدام بازو (۳ بازو، ۱۲۰ درجه از هم)
   + gr * 4.4                                              // هرچه دورتر، بیشتر پیچیده: این مارپیچ را می‌سازد
   + (Math.random() - .5) * (.9 - .5 * gr)                 // پراکندگی، نزدیک مرکز بیشتر

// fx.js خط ۴۶: هر فریم
ga' = ga + rot * (1.5 / (gr + .3))                         // چرخش تفاضلی
gx0 = cos(ga') * gr;   gy0 = sin(ga') * gr * .58          // بیضی: از زاویه دیده می‌شود
gx  = gx0*cos(tilt) - gy0*sin(tilt)                        // ماتریس دوران ۲ بعدی، tilt = -.38
gy  = gx0*sin(tilt) + gy0*cos(tilt)
فقط شکل B (A و B هر دو کهکشان هستند).

۷. تبدیل A به B: mix و m

// fx.js خط ۳۷-۳۸
want = Math.max(hover, ai ? 1 : 0)          // ۱ = می‌خواهیم کهکشان باشد
mix += (want - mix) * .045 * dt             // هر فریم ۴٫۵٪ از فاصله‌ی باقی‌مانده را طی کن

// fx.js خط ۴۷-۴۸
m = clamp(mix * 1.7 - gr * .7, 0, 1)        // سهم همین نقطه از تبدیل
m = m * m * (3 - 2 * m)                     // smoothstep: شروع و پایان نرم
x = cx + R * (ox + (gx - ox) * m)           // lerp بین A و B

دو لایه‌ی تبدیل داریم:

  1. mix (سراسری) یک عدد بین ۰ و ۱ است. با فرمول «نرم‌کردن نمایی» به سمت want می‌رود: هر فریم کسر ثابتی از فاصله‌ی باقی‌مانده را طی می‌کند، پس اول سریع است و آخرش آرام. با ۰٫۰۴۵ حدود یک ثانیه طول می‌کشد تا ۹۵٪ راه طی شود.
  2. m (برای هر نقطه): نقطه‌ای که gr کمتری دارد (نزدیک مرکز) زودتر شروع می‌کند. وقتی mix = 0.5 است، نقطه‌ی مرکز m = 0.85 دارد ولی نقطه‌ی لبه m = 0.15. برای همین تبدیل مثل موجی از مرکز به بیرون پخش می‌شود. ضریب ۱٫۷ = ۱ + ۰٫۷ تضمین می‌کند که در mix = 1 حتی دورترین نقطه هم به ۱ برسد.
m = smoothstep( mix · (1 + stagger) − key · stagger )

در Pixell عدد ۰٫۷ اسم stagger گرفته و key با گزینه‌ی order عوض می‌شود: از مرکز (اصلی)، از لبه، تصادفی، از چپ به راست، یا همه با هم.

mix را دستی جابه‌جا کنید و ببینید هر نقطه کی راه می‌افتد.

۸. سرعت و چرخش

// fx.js خط ۳۸
spd += ((ai ? 3.2 : 1 + mix * 2.6) - spd) * .04 * dt
rot += .0035 * spd * dt * (reduce ? .25 : 1)

۹. واکنش به ماوس

// fx.js خط ۴۹-۵۰
dx = x - mx;  dy = y - my;  d2 = dx*dx + dy*dy
if (d2 < 3600) {                         // 3600 = 60²، یعنی شعاع ۶۰ پیکسل
  d = sqrt(d2) || 1
  f = (3600 - d2) / 3600 * 14            // زیر ماوس ۱۴px، در لبه‌ی دایره ۰
  x += dx / d * f;  y += dy / d * f      // در جهت دور از ماوس
}
ماوس را روی بوم حرکت دهید. «نرمی دنبال‌کردن» کم یعنی نقطه‌ها با تأخیر و کش‌دار برمی‌گردند.

۱۰. رسم: نرمی، چشمک، هاله، نور

// fx.js خط ۵۱: نقطه به هدفش نمی‌پرد، هر فریم ۲۲٪ راه را می‌رود
p.x += (x - p.x) * Math.min(1, .22 * dt)
// خط ۵۲: چشمک با سینوس، هر نقطه فاز خودش را دارد (tw)
ctx.globalAlpha = .45 + .45 * Math.sin(now / 650 + p.tw)
// خط ۵۳: اندازه در حالت کهکشان کمی بزرگ‌تر
ctx.arc(p.x, p.y, p.sz * (.85 + .35 * m), 0, 2π)
// خط ۴۰-۴۳: گرادیان شعاعی آبی پشت نقطه‌ها، با mix پررنگ‌تر و بزرگ‌تر
ctx.globalCompositeOperation = "lighter"

۱۱. کارایی

۱۲. جدول همه‌ی پارامترها

ستون «اصلی» عدد ثابتی است که در fx.js نوشته شده. پیش‌فرض Pixell همیشه همان است.

گزینهاصلیمعنی
n380 (در AI: 260)تعداد نقطه‌ها
from / todisc / galaxyشکل A و B: disc, galaxy, ring, sphere, heart, star, grid, helix, text
arms3تعداد بازوهای کهکشان
twist4.4پیچش بازو از مرکز تا لبه (رادیان)
scatter0.9پراکندگی دور بازو
radiusBias0.65توان توزیع شعاعی
diffRot1.5شدت چرخش تفاضلی
flatten0.58ضریب y (بیضی‌شدن)
tilt-0.38زاویه‌ی کج (رادیان، حدود ۲۲- درجه). برای کره محور دید است.
radius0.44شعاع شکل نسبت به ضلع کوچک بوم
sizeMin, sizeRange, sizeGrow0.5, 1.5, 0.35شعاع نقطه = (min + rnd×range) × (0.85 + grow×m)
speed0.0035چرخش پایه در هر فریم (منفی = برعکس)
hoverBoost2.6سرعت اضافه در کهکشان کامل
boostSpeed3.2سرعت در حالت AI
ease0.045سرعت حرکت mix به سمت هدف
speedEase0.04سرعت تغییر spd
follow0.22سرعت رسیدن نقطه به جای هدفش
stagger0.7چقدر تبدیل موجی باشد
ordercentercenter, edge, random, sweep, none
triggerhoverhover, click, auto, manual, always, never
autoPeriod / manual—ثانیه‌های هر حالت در auto / مقدار mix در manual
boostکلاس fx-aiاجبار به کهکشان و سرعت AI. کلاس body.fx-ai هنوز هم کار می‌کند.
mouserepelrepel, attract, swirl, off
mouseRadius, mouseForce60, 14شعاع و قدرت اثر ماوس (پیکسل)
clickShock, shockForce— (جدید)موج ضربه با کلیک
palettecodebeatsنام پالت یا آرایه‌ای از رنگ‌ها، مثلاً ["#fff","#f0f"]
colorModecyclecycle, random, radius, angle
blendlighterهر globalCompositeOperation معتبر
glow, glowStrengthtrue, 1هاله‌ی مرکزی (رنگش از سه رنگ اول پالت)
twinkle, alphaBase, alphaAmp650, 0.45, 0.45چشمک: alpha = base + amp × sin(now/twinkle + phase)
trail0 (جدید)۰ = پاک‌کردن کامل هر فریم. نزدیک ۱ = دنباله‌ی بلند.
text, textFont— (جدید)برای شکل text. فارسی هم کار می‌کند.
hostcanvasعنصری که رویدادهای ماوس را می‌گیرد
reduceMotionتنظیم سیستمnull = از سیستم‌عامل، یا true/false

۱۳. استفاده در صفحه‌ی خودتان (API)

<canvas id="g" style="width:300px;height:300px"></canvas>
<script src="galaxy.js"></script>
<script>
  const g = PixellGalaxy(document.getElementById("g"), {to: "sphere", palette: "aurora"});

  g.set({arms: 5, trail: .8});    // هر گزینه را هر لحظه عوض کنید
  g.toggle();                     // برای trigger: "click" جابه‌جا می‌کند
  g.shock();                      // موج ضربه از مرکز (یا g.shock(x, y))
  g.scramble();                   // نقطه‌ها را در کل بوم پخش کن تا خودشان برگردند
  g.state();                      // {mix, spd, rot, fps, n}
  g.options();                    // کپی تنظیمات فعلی
  g.destroy();                    // توقف و حذف رویدادها
</script>
اندازه‌ی بوم را با CSS بدهید (width/height). موتور خودش تعداد پیکسل واقعی را با dpr تنظیم می‌کند. در آزمایشگاه دکمه‌ی «کپی کد استفاده» همین کد را با تنظیمات فعلی شما می‌سازد.

۱۴. ساختن شکل جدید

یک شکل فقط یک تابع است. برای نقطه‌ی p و زاویه‌ی rot، مختصات واحد را در out می‌نویسد: x و y تقریباً بین ۱- و ۱ (y رو به پایین) و z عمق بین ۱- و ۱ (برای شکل تخت ۰). نقطه‌ی جلوتر (z مثبت) بزرگ‌تر و پررنگ‌تر رسم می‌شود.

// p: {i, N, u = (i+.5)/N, r1, r2, r3, r4}  (r ها عدد تصادفی ثابت هر نقطه‌اند)
PixellGalaxy.shapes.flower = (p, rot, o, out) => {
  const a = p.u * Math.PI * 2 + rot;
  const r = Math.cos(5 * a) * .9 * (p.r2 < .7 ? 1 : Math.sqrt(p.r3));   // گل رز ۵ گلبرگ
  out.x = Math.cos(a) * r;  out.y = Math.sin(a) * r;  out.z = 0;
};
g.set({to: "flower"});
همین شکل flower که بالا تعریف شد، با trigger خودکار. ضریب گلبرگ هم یک گزینه‌ی دلخواه (petals) است.

در آزمایشگاه شکل جدید را در فهرست SHAPES داخل playground.js اضافه کنید تا در منو ظاهر شود.

۱۵. تمرین‌ها

  1. در آزمایشگاه stagger را ۰ کنید. تبدیل چه فرقی می‌کند؟ چرا؟ (راهنما: فرمول m)
  2. diffRot را ۰ کنید و چند ثانیه صبر کنید. بازوها دیگر پیچیده‌تر نمی‌شوند. چرا؟
  3. follow را ۰٫۰۳ و mouseForce را ۶۰ کنید. نتیجه را با اصل مقایسه کنید.
  4. blend را از lighter به «معمولی» ببرید و به مرکز کهکشان نگاه کنید.
  5. شکل spiral بسازید که در آن r = p.u و a = p.u * 20 + rot باشد (مارپیچ ارشمیدسی ساده).
  6. در galaxy.js کاری کنید که رنگ هر نقطه به جای ثابت، با m از رنگ A به رنگ B برود. (راهنما: دو پالت و درون‌یابی RGB)