馃幆 Budowa Interaktywnej Ko艂yski Newtona

Poci膮gnij kule poni偶ej lub kliknij na przyciski:

Czego si臋 nauczysz:

Krok 1: Struktura HTML

Zacznij od prostej struktury HTML zawieraj膮cej element canvas i przyciski steruj膮ce:

<canvas id="canvas" width="600" height="400"></canvas> <div class="controls"> <button onclick="startSwing(1)">Swing 1 Ball</button> <button onclick="startSwing(2)">Swing 2 Balls</button> <button onclick="reset()">Reset</button> </div>

Krok 2: Inicjalizacja Canvas i Sta艂ych

Skonfiguruj kontekst canvas i zdefiniuj fizyczne parametry ko艂yski:

const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); const numBalls = 5; // Liczba kul w ko艂ysce const ballRadius = 20; // Promie艅 ka偶dej kuli const stringLength = 150; // D艂ugo艣膰 linki wahad艂a const frameY = 50; // Pozycja Y g贸rnej ramy const spacing = ballRadius * 2 + 2; // Odst臋p mi臋dzy kulami const centerX = canvas.width / 2; // 艢rodek canvas let balls = []; let dragging = null; // 艢led藕, kt贸ra kula jest przeci膮gana
馃挕 Wskaz贸wka: Odst臋p mi臋dzy kulami jest nieco wi臋kszy ni偶 ich 艣rednica, aby zapobiec nak艂adaniu si臋, gdy s膮 w spoczynku.

Krok 3: Utworzenie Klasy Ball

W艂a艣ciwo艣ci Kuli

Ka偶da kula jest wahad艂em z punktem zakotwiczenia, k膮tem i pr臋dko艣ci膮:

class Ball { constructor(index) { this.index = index; this.anchorX = centerX + (index - 2) * spacing; this.anchorY = frameY; this.angle = 0; // Aktualny k膮t od pionu this.velocity = 0; // Pr臋dko艣膰 k膮towa this.mass = 1; this.stringLength = stringLength; } }

Obliczanie Pozycji

Konwersja wsp贸艂rz臋dnych biegunowych (k膮t) na wsp贸艂rz臋dne kartezja艅skie (x, y):

get x() { return this.anchorX + Math.sin(this.angle) * this.stringLength; } get y() { return this.anchorY + Math.cos(this.angle) * this.stringLength; }
Koncepcja Fizyczna: U偶ywamy sinusa i cosinusa, aby przekonwertowa膰 k膮t wahad艂a na wsp贸艂rz臋dne ekranu. K膮t 0 reprezentuje kul臋 wisz膮c膮 prosto w d贸艂.

Krok 4: Implementacja Fizyki Wahad艂a

Metoda Update

Zastosuj fizyk臋 prostego wahad艂a, aby zaktualizowa膰 pozycj臋 kuli w ka偶dej klatce:

update(dt) { const gravity = 0.5; const damping = 0.999; // Oblicz przyspieszenie z grawitacji let acceleration = (-gravity / this.stringLength) * Math.sin(this.angle); // Zaktualizuj pr臋dko艣膰 i zastosuj t艂umienie this.velocity += acceleration * dt; this.velocity *= damping; // Zaktualizuj k膮t this.angle += this.velocity * dt; }
Wz贸r Wahad艂a:
przyspieszenie = -(g / L) 脳 sin(胃)
Rozbicie na cz臋艣ci:

Krok 5: Renderowanie Kul

Narysuj ka偶d膮 kul臋 i jej link臋 na canvas:

draw() { // Narysuj link臋 ctx.strokeStyle = '#333'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(this.anchorX, this.anchorY); ctx.lineTo(this.x, this.y); ctx.stroke(); // Narysuj kul臋 ctx.fillStyle = '#4a5568'; ctx.beginPath(); ctx.arc(this.x, this.y, ballRadius, 0, Math.PI * 2); ctx.fill(); // Dodaj pod艣wietlenie dla efektu 3D ctx.fillStyle = 'rgba(255,255,255,0.3)'; ctx.beginPath(); ctx.arc(this.x - 5, this.y - 5, ballRadius / 3, 0, Math.PI * 2); ctx.fill(); }

Krok 6: Wykrywanie Kolizji

Wykryj, kiedy kule zderzaj膮 si臋 i przeka偶 p臋d mi臋dzy nimi:

function detectCollisions() { for (let i = 0; i < balls.length - 1; i++) { const b1 = balls[i]; const b2 = balls[i + 1]; // Oblicz odleg艂o艣膰 mi臋dzy 艣rodkami kul const dx = b2.x - b1.x; const dy = b2.y - b1.y; const dist = Math.sqrt(dx * dx + dy * dy); // Je艣li kule si臋 stykaj膮 if (dist < ballRadius * 2) { // Zamie艅 pr臋dko艣ci (kolizja spr臋偶ysta) const temp = b1.velocity; b1.velocity = b2.velocity; b2.velocity = temp; } } }
Koncepcja Fizyczna: W doskonale spr臋偶ystym zderzeniu mi臋dzy obiektami o r贸wnej masie, po prostu wymieniaj膮 si臋 pr臋dko艣ciami. To w艂a艣nie tworzy kultowy efekt ko艂yski Newtona!

Krok 7: P臋tla Animacji

Utw贸rz g艂贸wn膮 p臋tl臋 animacji, kt贸ra aktualizuje i renderuje wszystko:

function animate() { const dt = 0.5; // Krok czasowy dla fizyki // Aktualizuj wszystkie kule (z wyj膮tkiem tej przeci膮ganej) balls.forEach(ball => { if (dragging !== ball) { ball.update(dt); } }); // Sprawd藕 kolizje detectCollisions(); // Renderuj wszystko draw(); // Popro艣 o nast臋pn膮 klatk臋 requestAnimationFrame(animate); }
馃挕 Wskaz贸wka: requestAnimationFrame automatycznie synchronizuje si臋 z cz臋stotliwo艣ci膮 od艣wie偶ania przegl膮darki (zazwyczaj 60 FPS) dla p艂ynnej animacji.

Krok 8: Funkcja Rysowania

Wyczy艣膰 canvas i narysuj wszystkie elementy:

function draw() { // Wyczy艣膰 canvas ctx.clearRect(0, 0, canvas.width, canvas.height); // Narysuj g贸rn膮 ram臋 ctx.strokeStyle = '#2d3748'; ctx.lineWidth = 4; ctx.beginPath(); ctx.moveTo(50, frameY); ctx.lineTo(canvas.width - 50, frameY); ctx.stroke(); // Narysuj wszystkie kule balls.forEach(ball => ball.draw()); }

Krok 9: Interakcja Mysz膮

Przycisk Myszy w D贸艂 - Rozpocznij Przeci膮ganie

canvas.addEventListener('mousedown', e => { const rect = canvas.getBoundingClientRect(); const mx = e.clientX - rect.left; const my = e.clientY - rect.top; // Sprawd藕, czy mysz jest nad kt贸r膮kolwiek kul膮 for (let ball of balls) { const dx = mx - ball.x; const dy = my - ball.y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance < ballRadius) { dragging = ball; ball.velocity = 0; // Zatrzymaj jej ruch break; } } });

Ruch Myszy - Aktualizacja Pozycji Kuli

canvas.addEventListener('mousemove', e => { if (dragging) { const rect = canvas.getBoundingClientRect(); const mx = e.clientX - rect.left; const my = e.clientY - rect.top; // Oblicz k膮t od kotwicy do myszy const dx = mx - dragging.anchorX; const dy = my - dragging.anchorY; dragging.angle = Math.atan2(dx, dy); } });

Przycisk Myszy w G贸r臋 - Zwolnij Kul臋

canvas.addEventListener('mouseup', () => { dragging = null; }); canvas.addEventListener('mouseleave', () => { dragging = null; });

Krok 10: Obs艂uga Dotyku

Dodaj obs艂ug臋 ekranu dotykowego z podobn膮 logik膮 do zdarze艅 myszy:

canvas.addEventListener('touchstart', e => { e.preventDefault(); // Zapobiegaj przewijaniu const rect = canvas.getBoundingClientRect(); const touch = e.touches[0]; const mx = touch.clientX - rect.left; const my = touch.clientY - rect.top; for (let ball of balls) { const dx = mx - ball.x; const dy = my - ball.y; if (Math.sqrt(dx * dx + dy * dy) < ballRadius) { dragging = ball; ball.velocity = 0; break; } } }); canvas.addEventListener('touchmove', e => { e.preventDefault(); if (dragging) { const rect = canvas.getBoundingClientRect(); const touch = e.touches[0]; const mx = touch.clientX - rect.left; const my = touch.clientY - rect.top; const dx = mx - dragging.anchorX; const dy = my - dragging.anchorY; dragging.angle = Math.atan2(dx, dy); } }); canvas.addEventListener('touchend', () => { dragging = null; }); canvas.addEventListener('touchcancel', () => { dragging = null; });
馃挕 Wa偶ne: Zawsze wywo艂uj e.preventDefault() dla zdarze艅 dotykowych, aby zapobiec przewijaniu strony podczas przeci膮gania.

Krok 11: Funkcje Pomocnicze

Inicjalizacja Ko艂yski

function init() { balls = []; for (let i = 0; i < numBalls; i++) { balls.push(new Ball(i)); } }

Rozpocznij Automatyczne Wahni臋cie

function startSwing(count) { reset(); for (let i = 0; i < count; i++) { balls[i].angle = -0.6; // Odchyl o 0.6 radiana } }

Zresetuj Wszystkie Kule

function reset() { balls.forEach(ball => { ball.angle = 0; ball.velocity = 0; }); }

Krok 12: Uruchomienie Symulacji

Na koniec zainicjalizuj kule i uruchom p臋tl臋 animacji:

init(); animate();

馃帗 Podsumowanie Kluczowych Koncepcji

Symulacja Fizyczna

Techniki Canvas

Interakcja

馃殌 Pomys艂y na Ulepszenia

Wi臋cej samouczk贸w JS

Gra "Wie偶a" - 84 linie JavaScript

Fraktale - 25 linii

Sinus scroll - 30 linii

Gra "Angry Chickens"

Animowane krzywe kwadratowe - 40 linii

Animowane konstelacje cz膮steczek - 42 linie

Eksperyment z enginem fizyki