ballpit-js, bir hero bölümünü küçük bir fizik dünyasına çeviren, three.js dışında bağımlılığı olmayan tek dosyalık bir JavaScript kütüphanesi. Küreler kutunun içine yağıyor, birbirinin üstüne yığılıp gerçek bir yığın oluşturuyor ve imleç aralarından geçtiğinde dağılıyor. Bu sitenin ana sayfasındaki hero için yazıldı, sonra ayıklanıp GitHub’da MIT lisansıyla yayınlandı.
Canlı halini iki yerde görebilirsin: bu sitedeki Ballpit sayfası ve kütüphanenin kendi tanıtım sayfası. İkisinde de ayar paneli açık — top sayısını, boyutları ve imleç itmesini kendin oynatabilirsin. Aşağıda hem nasıl çalıştığını hem de yol boyunca çıkan üç somut hatayı anlatıyoruz; ikincisi muhtemelen daha faydalı.
Neden Döngüye Alınmış Bir Video Değil?
Dekoratif hero arka planlarının çoğu ya döngüye alınmış bir video ya da bir shader. İkisi de güzel görünüyor ama dokunduğunda hiçbir şey olmuyor: piksel olarak zengin, davranış olarak ölüler. Buradaki fark, ekrandaki şeyin bir kayıt değil çalışan bir simülasyon olması. Toplar üst üste biniyor, şeklini koruyor ve onları nereye ittiysen orada kalıyor.
Amaç ölçülebilir: ziyaretçiye kaydırıp geçeceği bir görüntü yerine kurcalayacağı bir şey vermek. Yatağın son hali her ziyaretçide farklı, çünkü onu ziyaretçi şekillendiriyor.
Çözücü: Verlet Entegrasyonu ve Konumsal Gevşetme
Kütüphane bir fizik motoru paketlemiyor; ihtiyaç duyduğu kadarını kendi yapıyor. Her top için hız değil, önceki konum saklanıyor (Verlet entegrasyonu): hız iki konum arasındaki farktan türüyor. Bunun pratik faydası, çakışmaları doğrudan konumu iterek çözebilmek — düzeltme bir sonraki karede kendiliğinden hıza dönüşüyor.
Temas çözümü konumsal gevşetme (positional relaxation) ile yapılıyor: her adımda çakışan çiftler birkaç tur boyunca birbirinden itiliyor, yığın da böyle şeklini koruyor. Çiftleri bulmak için tek tip ızgara (uniform grid) tabanlı bir geniş faz kullanılıyor, yani maliyet top sayısının karesiyle değil kendisiyle ölçekleniyor. Zaman adımı sabit; kare süresi dalgalandığında simülasyon hızlanıp yavaşlamasın diye bir biriktirici (accumulator) üzerinden ilerliyor.
Ortografik Kamera: Kenarlardaki Bozulmayı Bitiren Ayar
İlk sürüm perspektif kamerayla çalışıyordu ve alanın kenarlarına gidildikçe küreler gözle görülür biçimde eliptikleşiyordu. Sebep basit: perspektif projeksiyonda kenardaki bir küreye açıyla bakılır, o da elips olarak izdüşer. Çözüm OrthographicCamera‘ya geçmek — ortografik projeksiyonda her küre, ekranın neresinde olursa olsun aynı dik açıyla görünüyor.
Görüş yüksekliği, eski perspektif kameranın 40 derecelik görüş açısıyla 7,4 birim mesafede kapladığı alandan hesaplandı; böylece kamera değişirken sahnenin ölçeği aynı kaldı.
Küreleri Saran Beyaz Halka Bir Işık Hatası Değil, Fresnel’di
Bir ara her kürenin siluetini ince beyaz bir halka sarıyordu; ışıkları kısmak işe yaramıyordu, çünkü sorun ışıklarda değildi. Dielektrik bir yüzeyin yansıtıcılığı sıyırma açılarında (grazing angles) 1’e doğru tırmanır — yani kürenin kenarı ortam haritasını neredeyse ayna gibi yansıtır. Ortam parlak olduğu için bu, çizilmiş bir kontur gibi okunuyordu. Üstüne bir de clearcoat katmanı ikinci bir Fresnel lobu ekliyordu.
Halkayı kaldıran şey üç değeri birlikte kısmak oldu: ortam yoğunluğu (envMapIntensity) 0,45; sıyırma açısı yansıtıcılığı (specularIntensity, F90’ı ölçekler) 0,3; clearcoat 0,12. Camsı parlaklık kalıyor, kontur gidiyor. Bu üçü kütüphanede tek tek ayarlanabilir seçenekler — daha ıslak, daha aynamsı toplar isteyen birlikte yükseltebilir.
İmleç İtmesinin Çalışan Aralığı Sandığımızdan Dar
pointer.strength, bir topun tek adımda en fazla ne kadar yer değiştirebileceğinin katı üst sınırı. Bu değerin doğru aralığı beklenenden dar ve paketlenme sıklığı değiştiğinde yeniden ayarlanması gerekiyor. İki uç da ölçüldü:
- Fazla yüksek (0,04): tek bir sürükleme yatağı toplayıp tümsek yapıyordu. Sürükleme başına ortalama yer değiştirme 357 piksel, yatak derinliği 141 pikselden 477 piksele çıkıyordu — ve yanal çekim sıfır olduğu için bunu geri alan hiçbir kuvvet yok.
- Fazla düşük (0,005): sıkı paketlenmiş yığın itmeyi yutuyordu. Komşu temasları aynı adım içinde düzeltmeyi iptal ediyor, gözle görülür hiçbir şey olmuyordu.
- Seçilen (0,02, yarıçap 1,45 ≈ 157 piksel): yakındaki topları birkaç kendi genişliği kadar kenara itiyor, tekrarlanan sürüklemelerde yatağın şeklini bozmuyor.
Buradaki asıl ders, değeri gözle ayarlamanın yanıltıcı olması. “Bir şey oluyor” hissi ile ölçülen yer değiştirme birbirini tutmuyordu; sayılara bakmadan önce iki kez yanlış ayarlandı.

controls: true ile geliyor, etiketleri çevrilebildiği için sitede Türkçe görünüyor.Performans: Ölçülen Sayılar
Bütün toplar tek bir küre geometrisini ve altı paylaşılan malzemeyi kullanıyor. Bir M4 Max üzerinde, kare başına adım artı render maliyeti:
- 120 top: 0,22 ms — 60 fps bütçesinin yüzde 1’i
- 180 top: 0,39 ms — yüzde 2’si
- 400 top: 0,77 ms — yüzde 5’i
Bunlar tek bir makinenin sayıları. Zayıf bir tümleşik GPU’da birkaç kat yavaş olur ve asıl maliyeti fizik değil fragment aşaması yaratır; bu yüzden cihaz piksel oranı varsayılan olarak 1,5’te sınırlanıyor. Kütüphane ayrıca gerçek kare sürelerini ölçüyor ve medyan bütçeyi (varsayılan 8 ms) aşarsa top sayısını yüzde 60’a düşürüyor. Sayı kaydırıcısını ziyaretçiye açtıysan, ziyaretçi ona dokunduğu anda bu emniyet ağı devreden çıkıyor — kullanıcının tercihi kazanıyor.
Kurulum ve Kullanım
Paket henüz npm’de değil; depodan kuruluyor ya da src/ballpit.js dosyası doğrudan projeye kopyalanıyor.
npm install github:ozdalgic/ballpit-js three
import { createBallpit } from 'ballpit-js';createBallpit(document.querySelector('#hero-pit'), { count: 180, palette: ['#eaf1ff', '#2957ff', '#00509f'],});
Kapsayıcının kendi ölçüsü olması gerekiyor; canvas onu dolduruyor. Dikkat edilecek tek şey count‘un bir yoğunluk değil mutlak sayı olması: 1440 piksel genişliğinde iki top derinliğinde bir yatak olarak okunan sayı, telefon genişliğindeki bir hero’yu tamamen gömüyor. Tanıtım sayfası bu yüzden 760 pikselin altında 130 yerine 42 topa düşüyor.
Erişilebilirlik tarafında prefers-reduced-motion: reduce ayarı açıksa animasyon yerine tek bir oturmuş kare çiziliyor, canvas hiçbir zaman içeriğin işaretçi olaylarını çalmıyor ve WebGL yoksa kütüphane hata fırlatmak yerine uyarı verip boş bir denetleyici döndürüyor — sayfa etkilenmiyor.

Three.js performansı konusunu daha derinlemesine merak ediyorsan Three.js’te Piksel Bütçeleme yazımıza; benzer ölçekte bir başka tarayıcı içi 3B projeye Model X Studio yazımıza; modül tarafındaki yeni tarayıcı desteğine ise Safari Sonunda Top-Level Await’i Tam Destekliyor yazımıza bakabilirsin.
Sık Sorulan Sorular
ballpit-js kaç top kaldırabiliyor?
Ölçülen maliyet 400 topta bile bir M4 Max üzerinde kare başına 0,77 milisaniye, yani 60 fps bütçesinin yüzde 5’i. Sınırı belirleyen fizik değil fragment aşaması olduğu için zayıf GPU’larda bu sayı belirgin biçimde yükselir. Varsayılan autoQuality ayarı gerçek kare sürelerini ölçüp gerekirse top sayısını kendiliğinden düşürüyor.
Perspektif kamera yerine neden ortografik kamera kullanılıyor?
Perspektif projeksiyonda alanın kenarındaki kürelere açıyla bakılır ve bunlar eliptik görünür. Ortografik kamerada her küre, ekranın neresinde olursa olsun aynı dik açıyla izdüşer; geniş bir hero şeridinde fark açıkça görülüyor.
Kürelerin kenarındaki beyaz halka nasıl kaldırılır?
O halka bir ışık hatası değil, Fresnel etkisi: dielektrik yüzeyin yansıtıcılığı sıyırma açılarında 1’e yaklaşır ve kenar, ortam haritasını ayna gibi yansıtır. Ortam yoğunluğunu, sıyırma açısı yansıtıcılığını ve clearcoat’ı birlikte kısmak (0,45 / 0,3 / 0,12) halkayı kaldırırken camsı parlaklığı koruyor.
Kütüphaneyi kendi sitemde kullanabilir miyim?
Evet, MIT lisanslı. GitHub deposundan kurabilir ya da tek dosyalık kaynağı doğrudan projene kopyalayabilirsin; three.js dışında bağımlılığı yok.







Bir Yorum Yazın