کهکشان ذرهای: چطور کار میکند؟
این انیمیشن در 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←
هل ماوس←
حرکت نرم و رسم
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);
requestAnimationFrame: مرورگر قبل از هر بار نقاشی صفحه (معمولاً ۶۰ بار در ثانیه) تابع را صدا میزند وnowرا به میلیثانیه میدهد.dt: فاصلهی دو فریم تقسیم بر ۱۶٫۶۷ms (یک فریم در ۶۰fps). در ۶۰fps برابر ۱ است و در ۳۰fps برابر ۲. همهی سرعتها درdtضرب میشوند تا انیمیشن روی صفحهی ۱۲۰ هرتز تندتر و روی سیستم کند آهستهتر نشود. سقف ۵۰ms جلوی پرش بزرگ بعد از برگشتن به تب را میگیرد.dpr: روی صفحههای رتینا هر پیکسل CSS چند پیکسل واقعی است. بوم باw*dprپیکسل ساخته میشود تا تیز باشد وsetTransformباعث میشود بقیهی کد با واحد CSS کار کند. سقف ۲ برای کارایی است.- اگر بوم از صفحه حذف شود (
isConnected)، حلقه برای همیشه متوقف میشود. اگر بوم دیده نشود یا تب پنهان باشد، فقط آن فریم رد میشود.
۵. شکل 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;
این همان الگوی چیدمان دانههای گل آفتابگردان است. دو ترفند دارد:
- زاویهی طلایی ۲٫۳۹۹۹۶ رادیان (۱۳۷٫۵ درجه): هر نقطه نسبت به قبلی اینقدر میچرخد. چون این عدد «گنگترین» نسبت ممکن به دور کامل است، نقطهها هیچوقت روی یک خط (پره) نمیافتند و فضا یکنواخت پر میشود.
- جذر در شعاع: مساحت دایره با r² رشد میکند. اگر شعاع خطی بود، مرکز شلوغ و لبه خالی میشد. با
sqrtتراکم همهجا یکسان است.
۶. شکل 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)
- توزیع شعاعی
pow(random, .65): توان کمتر از ۱ عددهای تصادفی را به سمت ۱ میبرد، پس ستارههای بیشتری در لبه هستند و مرکز خیلی پرنور نمیشود. در آزمایشگاه اسمشradiusBiasاست. - بازوها:
i % ARMSذرهها را به نوبت بین بازوها پخش میکند. - پیچش (
twist): زاویه با فاصله زیاد میشود (gr*4.4). نقطهای که دورتر است بیشتر دور مرکز پیچیده، و حاصل یک مارپیچ ارشمیدسی است. - چرخش تفاضلی (
diffRot):1.5/(gr+.3)برای مرکز (gr=0) برابر ۵ است و برای لبه (gr=1) حدود ۱٫۱۵. یعنی مرکز تقریباً ۴ برابر سریعتر میچرخد، مثل کهکشان واقعی، و بازوها با گذر زمان پیچیدهتر به نظر میرسند. عدد.3جلوی تقسیم بر صفر را میگیرد. - پهنشدگی و کجی: ضرب y در ۰٫۵۸ دایره را بیضی میکند (انگار از کنار نگاهش میکنیم). ماتریس دوران، بیضی را ۲۲ درجه کج میکند.
۷. تبدیل 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
دو لایهی تبدیل داریم:
mix(سراسری) یک عدد بین ۰ و ۱ است. با فرمول «نرمکردن نمایی» به سمتwantمیرود: هر فریم کسر ثابتی از فاصلهی باقیمانده را طی میکند، پس اول سریع است و آخرش آرام. با ۰٫۰۴۵ حدود یک ثانیه طول میکشد تا ۹۵٪ راه طی شود.m(برای هر نقطه): نقطهای کهgrکمتری دارد (نزدیک مرکز) زودتر شروع میکند. وقتیmix = 0.5است، نقطهی مرکزm = 0.85دارد ولی نقطهی لبهm = 0.15. برای همین تبدیل مثل موجی از مرکز به بیرون پخش میشود. ضریب ۱٫۷ = ۱ + ۰٫۷ تضمین میکند که درmix = 1حتی دورترین نقطه هم به ۱ برسد.
در Pixell عدد ۰٫۷ اسم stagger گرفته و key با گزینهی order عوض میشود: از مرکز (اصلی)، از لبه، تصادفی، از چپ به راست، یا همه با هم.
۸. سرعت و چرخش
// fx.js خط ۳۸
spd += ((ai ? 3.2 : 1 + mix * 2.6) - spd) * .04 * dt
rot += .0035 * spd * dt * (reduce ? .25 : 1)
rotیک زاویهی مشترک است که همیشه زیاد میشود. هر دو شکل آن را به زاویهی نقطهها اضافه میکنند.- سرعت هدف در حالت آرام ۱ است، در کهکشان کامل ۳٫۶ (۱ + ۲٫۶) و وقتی AI کار میکند ۳٫۲. خود
spdهم با همان نرمکردن نمایی تغییر میکند، پس شتابگرفتن و ترمز طبیعی دیده میشود. - اگر کاربر «کاهش حرکت» را روشن کرده باشد، چرخش ۴ برابر کندتر است (دسترسپذیری).
۹. واکنش به ماوس
// 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 // در جهت دور از ماوس }
- مقایسه با
d2به جایdیعنی برای نقطههای دور جذر گرفته نمیشود. این یک بهینهسازی رایج است. dx/dوdy/dبردار واحد از ماوس به نقطه است وfطول هل. در Pixell میتوانید حالت را «جذب» (علامت منفی) یا «گرداب» (بردار عمود:-dy, dx) کنید.host: در CodeBeats کل کادر Develop ماوس را میگیرد، نه فقط بوم کوچک. برای همین کهکشان قبل از رسیدن ماوس به آن بیدار میشود.
۱۰. رسم: نرمی، چشمک، هاله، نور
// 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"
- دنبالکردن نرم: همان نرمکردن نمایی، این بار برای مکان. همین باعث میشود هل ماوس کشدار باشد و هیچ پرشی دیده نشود.
- چشمک: شفافیت بین ۰ و ۰٫۹ نوسان میکند. دورهی آن
2π × 650ms ≈ 4ثانیه است و هر نقطه با فاز تصادفیtwمستقل چشمک میزند. lighter: رنگها به جای پوشاندن هم جمع میشوند. جایی که نقطهها روی هم میافتند (مرکز کهکشان) روشنتر میشود، مثل نور واقعی.- رنگ: ۸ رنگ آبی در
PALو هر نقطه رنگi % 8را میگیرد.
۱۱. کارایی
- IntersectionObserver: وقتی بوم از دید خارج شده (اسکرول)، رسم متوقف میشود.
document.hiddenهمین کار را برای تب پنهان انجام میدهد. - ذرات یک بار ساخته میشوند و فقط مختصاتشان عوض میشود. در حلقه شیء جدیدی ساخته نمیشود، پس جمعآوری زباله (GC) کار زیادی ندارد.
- هر نقطه یک
beginPath/arc/fillاست. تا حدود ۳۰۰۰ نقطه روان است. برای دهها هزار نقطه باید به WebGL رفت. - در Pixell شمارندهی
fpsپایین چپ آزمایشگاه این را نشان میدهد. تعداد را تا ۵۰۰۰ ببرید و ببینید.
۱۲. جدول همهی پارامترها
ستون «اصلی» عدد ثابتی است که در fx.js نوشته شده. پیشفرض Pixell همیشه همان است.
| گزینه | اصلی | معنی |
|---|---|---|
n | 380 (در AI: 260) | تعداد نقطهها |
from / to | disc / galaxy | شکل A و B: disc, galaxy, ring, sphere, heart, star, grid, helix, text |
arms | 3 | تعداد بازوهای کهکشان |
twist | 4.4 | پیچش بازو از مرکز تا لبه (رادیان) |
scatter | 0.9 | پراکندگی دور بازو |
radiusBias | 0.65 | توان توزیع شعاعی |
diffRot | 1.5 | شدت چرخش تفاضلی |
flatten | 0.58 | ضریب y (بیضیشدن) |
tilt | -0.38 | زاویهی کج (رادیان، حدود ۲۲- درجه). برای کره محور دید است. |
radius | 0.44 | شعاع شکل نسبت به ضلع کوچک بوم |
sizeMin, sizeRange, sizeGrow | 0.5, 1.5, 0.35 | شعاع نقطه = (min + rnd×range) × (0.85 + grow×m) |
speed | 0.0035 | چرخش پایه در هر فریم (منفی = برعکس) |
hoverBoost | 2.6 | سرعت اضافه در کهکشان کامل |
boostSpeed | 3.2 | سرعت در حالت AI |
ease | 0.045 | سرعت حرکت mix به سمت هدف |
speedEase | 0.04 | سرعت تغییر spd |
follow | 0.22 | سرعت رسیدن نقطه به جای هدفش |
stagger | 0.7 | چقدر تبدیل موجی باشد |
order | center | center, edge, random, sweep, none |
trigger | hover | hover, click, auto, manual, always, never |
autoPeriod / manual | — | ثانیههای هر حالت در auto / مقدار mix در manual |
boost | کلاس fx-ai | اجبار به کهکشان و سرعت AI. کلاس body.fx-ai هنوز هم کار میکند. |
mouse | repel | repel, attract, swirl, off |
mouseRadius, mouseForce | 60, 14 | شعاع و قدرت اثر ماوس (پیکسل) |
clickShock, shockForce | — (جدید) | موج ضربه با کلیک |
palette | codebeats | نام پالت یا آرایهای از رنگها، مثلاً ["#fff","#f0f"] |
colorMode | cycle | cycle, random, radius, angle |
blend | lighter | هر globalCompositeOperation معتبر |
glow, glowStrength | true, 1 | هالهی مرکزی (رنگش از سه رنگ اول پالت) |
twinkle, alphaBase, alphaAmp | 650, 0.45, 0.45 | چشمک: alpha = base + amp × sin(now/twinkle + phase) |
trail | 0 (جدید) | ۰ = پاککردن کامل هر فریم. نزدیک ۱ = دنبالهی بلند. |
text, textFont | — (جدید) | برای شکل text. فارسی هم کار میکند. |
host | canvas | عنصری که رویدادهای ماوس را میگیرد |
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>
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 اضافه کنید تا در منو ظاهر شود.
۱۵. تمرینها
- در آزمایشگاه
staggerرا ۰ کنید. تبدیل چه فرقی میکند؟ چرا؟ (راهنما: فرمولm) diffRotرا ۰ کنید و چند ثانیه صبر کنید. بازوها دیگر پیچیدهتر نمیشوند. چرا؟followرا ۰٫۰۳ وmouseForceرا ۶۰ کنید. نتیجه را با اصل مقایسه کنید.blendرا از lighter به «معمولی» ببرید و به مرکز کهکشان نگاه کنید.- شکل
spiralبسازید که در آنr = p.uوa = p.u * 20 + rotباشد (مارپیچ ارشمیدسی ساده). - در
galaxy.jsکاری کنید که رنگ هر نقطه به جای ثابت، باmاز رنگ A به رنگ B برود. (راهنما: دو پالت و درونیابی RGB)