Berikut ini visual simulasi partikel yang bergerak acak dan memantul ketika mengenai dinding pembatas dan ketika bertumbukan dengan partikel lain. Ini relatif mudah dipahami karena setiap perbagian kode sudah diberi keterangan/komentar. Cocok buat pemula yang ingin mengawali pembuatan simulasi sederhana.
Contoh simulasi animasi seperti berikut kemudian diikuti dengan kode yang dapat disalin dan disimpan dalam format html di notepad++ sehingga dapat dibuka secara offline/luring. Unduh notepad++.
Simulasi Tumbukan Antarpartikel
dengan HTML+Canvas
Berikut ini kode yang dapat disalin ke format html, misal dengan menggunakan aplikasi notepad atau notepad++ atau yang lain di komputer kemudian disimpan sebagai file html (Hyper Text Markup Language). Bila sudah silakan buka file ini dengan menggunakan browser seperti Chrome, Edges, Opera, Safari, atau yang lain.
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<!-- Memastikan encoding karakter menggunakan UTF-8 untuk mendukung berbagai bahasa -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Mengatur viewport agar responsif di perangkat mobile, dengan skala awal 1.0 -->
<title>Simulasi Partikel dengan Canvas</title>
<!-- Judul halaman yang akan muncul di tab browser -->
<style>
/* Gaya dasar untuk container utama simulasi */
#particleSimContainer {
margin: 0;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
font-family: Arial, sans-serif;
background-color: #b3e6ff;
/* Mengatur container sebagai flexbox kolom, dengan konten terpusat dan latar abu-abu muda */
}
/* Gaya untuk judul */
h1 {
color: #333;
font-size: 24px;
margin-bottom: 10px;
/* Warna abu-abu gelap, ukuran font 24px, dan margin bawah untuk jarak */
}
/* Gaya untuk container tombol kontrol */
.particleSimControls {
margin: 15px 0;
/* Margin vertikal untuk memberi jarak dari elemen lain */
}
/* Gaya untuk tombol */
#particleSimAdd, #particleSimReset {
padding: 8px 15px;
margin: 0 5px;
cursor: pointer;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
font-size: 14px;
/* Tombol dengan padding, margin antar tombol, kursor pointer, warna hijau, teks putih, tanpa border, dan sudut membulat */
}
/* Gaya untuk canvas */
#particleSimCanvas {
background-color: white;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
margin-top: 20px;
/* Latar putih, bayangan halus untuk efek 3D, dan margin atas untuk jarak */
}
/* Media query untuk layar dengan lebar maksimum 414px (misalnya iPhone 6/7/8) */
@media only screen and (max-width: 414px) {
#particleSimContainer {
padding: 10px;
/* Kurangi padding untuk menghemat ruang di layar kecil */
}
#particleSimCanvas {
width: 350px;
height: 250px;
margin-top: 10px;
/* Ubah ukuran canvas agar muat di layar kecil dan kurangi margin atas */
}
.particleSimControls {
margin: 10px 0;
/* Kurangi margin vertikal untuk kontrol */
}
#particleSimAdd, #particleSimReset {
padding: 6px 10px;
font-size: 12px;
margin: 0 3px;
/* Kurangi padding, ukuran font, dan margin antar tombol untuk layar kecil */
}
h1 {
font-size: 18px;
margin-bottom: 8px;
/* Kurangi ukuran font judul dan margin bawah */
}
}
</style>
</head>
<body>
<!-- Container utama untuk seluruh simulasi -->
<div id="particleSimContainer">
<!-- Judul simulasi -->
<h1>Simulasi Tumbukan Antarpartikel
dengan HTML+Canvas</h1>
<!-- Container untuk tombol kontrol -->
<div class="particleSimControls">
<!-- Tombol untuk menambah partikel -->
<button id="particleSimAdd">Tambah Partikel</button>
<!-- Tombol untuk mereset simulasi -->
<button id="particleSimReset">Reset</button>
</div>
<!-- Elemen canvas tempat simulasi digambar -->
<canvas id="particleSimCanvas" width="600" height="400"></canvas>
</div>
<script>
// Membungkus semua kode dalam IIFE untuk mencegah polusi namespace global
(function() {
// Menunggu hingga DOM selesai dimuat sebelum menjalankan kode
document.addEventListener('DOMContentLoaded', function() {
// =============================================
// INISIALISASI CANVAS DAN KONTEKS
// =============================================
// Mengambil elemen canvas dari DOM berdasarkan ID
const canvas = document.getElementById('particleSimCanvas');
// Keluar jika canvas tidak ditemukan (untuk mencegah error)
if (!canvas) return;
// Mendapatkan konteks rendering 2D untuk menggambar di canvas
const ctx = canvas.getContext('2d');
// Fungsi untuk menyesuaikan ukuran canvas berdasarkan lebar layar
function resizeCanvas() {
// Jika lebar layar <= 414px, gunakan ukuran kecil
if (window.innerWidth <= 414) {
canvas.width = 350;
canvas.height = 250;
} else {
// Jika tidak, gunakan ukuran default
canvas.width = 600;
canvas.height = 400;
}
}
// Panggil fungsi resize saat inisialisasi
resizeCanvas();
// Tambahkan listener untuk menangani perubahan ukuran jendela
window.addEventListener('resize', resizeCanvas);
// =============================================
// VARIABEL GLOBAL DAN KONFIGURASI
// =============================================
// Array untuk menyimpan semua partikel dalam simulasi
let particles = [];
// Objek untuk menyimpan konfigurasi warna
const colors = {
background: '#f9f9f9', // Warna latar belakang canvas (abu-abu sangat muda)
particleColors: [ // Array warna untuk partikel (beragam warna cerah)
'#FF5252', '#FF4081', '#E040FB',
'#7C4DFF', '#536DFE', '#448AFF',
'#40C4FF', '#18FFFF', '#64FFDA',
'#69F0AE', '#B2FF59', '#EEFF41'
],
border: '#333' // Warna border partikel (abu-abu gelap)
};
// Objek untuk menyimpan konfigurasi simulasi
const config = {
maxParticles: 50, // Jumlah maksimum partikel yang diizinkan
minRadius: 5, // Radius minimum partikel
maxRadius: 15, // Radius maksimum partikel
minSpeed: 1, // Kecepatan minimum partikel
maxSpeed: 3 // Kecepatan maksimum partikel
};
// =============================================
// KELAS PARTIKEL
// =============================================
// Kelas untuk mengelola properti dan perilaku setiap partikel
class Particle {
// Konstruktor untuk inisialisasi partikel
constructor(x, y) {
// Posisi awal partikel (jika x/y tidak diberikan, gunakan posisi acak)
this.x = x || Math.random() * canvas.width;
this.y = y || Math.random() * canvas.height;
// Ukuran partikel (acak antara minRadius dan maxRadius)
this.radius = config.minRadius + Math.random() * (config.maxRadius - config.minRadius);
// Kecepatan horizontal (acak, bisa positif/negatif untuk arah berlawanan)
this.speedX = (config.minSpeed + Math.random() * (config.maxSpeed - config.minSpeed)) * (Math.random() < 0.5 ? -1 : 1);
// Kecepatan vertikal (sama seperti speedX)
this.speedY = (config.minSpeed + Math.random() * (config.maxSpeed - config.minSpeed)) * (Math.random() < 0.5 ? -1 : 1);
// Pilih warna acak dari particleColors
this.color = colors.particleColors[Math.floor(Math.random() * colors.particleColors.length)];
// Sudut rotasi awal untuk efek visual
this.rotation = 0;
// Kecepatan rotasi (acak, bisa positif/negatif)
this.rotationSpeed = Math.random() * 0.02 - 0.01;
}
// Method untuk memperbarui posisi dan memeriksa batas canvas
update() {
// Update posisi berdasarkan kecepatan
this.x += this.speedX;
this.y += this.speedY;
// Update sudut rotasi
this.rotation += this.rotationSpeed;
// Deteksi tumbukan dengan tepi kiri/kanan canvas
if (this.x - this.radius <= 0 || this.x + this.radius >= canvas.width) {
this.speedX *= -1; // Balik arah horizontal
// Koreksi posisi agar partikel tidak terjebak di tepi
if (this.x - this.radius <= 0) this.x = this.radius;
if (this.x + this.radius >= canvas.width) this.x = canvas.width - this.radius;
}
// Deteksi tumbukan dengan tepi atas/bawah canvas
if (this.y - this.radius <= 0 || this.y + this.radius >= canvas.height) {
this.speedY *= -1; // Balik arah vertikal
// Koreksi posisi
if (this.y - this.radius <= 0) this.y = this.radius;
if (this.y + this.radius >= canvas.height) this.y = canvas.height - this.radius;
}
}
// Method untuk menggambar partikel di canvas
draw() {
// Simpan state canvas sebelum transformasi
ctx.save();
// Pindahkan origin ke posisi partikel
ctx.translate(this.x, this.y);
// Rotasi canvas sesuai sudut partikel
ctx.rotate(this.rotation);
// Gambar lingkaran partikel
ctx.beginPath();
ctx.arc(0, 0, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color; // Warna isi
ctx.fill();
// Tambahkan border
ctx.lineWidth = 2;
ctx.strokeStyle = colors.border; // Warna border
ctx.stroke();
// Kembalikan state canvas ke semula
ctx.restore();
}
}
// =============================================
// FUNGSI UNTUK MENANGANI TABRAKAN ANTAR PARTIKEL
// =============================================
// Fungsi untuk mendeteksi dan menangani tabrakan antar partikel
function handleCollisions() {
// Iterasi melalui semua pasangan partikel
for (let i = 0; i < particles.length; i++) {
for (let j = i + 1; j < particles.length; j++) {
const p1 = particles[i];
const p2 = particles[j];
// Hitung jarak antar pusat partikel menggunakan rumus Euclidean
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
const distance = Math.sqrt(dx * dx + dy * dy);
// Cek apakah partikel bertabrakan (jarak < jumlah radius)
if (distance < p1.radius + p2.radius) {
// Hitung vektor normal (arah tabrakan)
const nx = dx / distance;
const ny = dy / distance;
// Hitung kecepatan relatif antar partikel
const dvx = p2.speedX - p1.speedX;
const dvy = p2.speedY - p1.speedY;
// Hitung dot product untuk proyeksi kecepatan pada vektor normal
const dot = dvx * nx + dvy * ny;
// Update kecepatan untuk simulasi pantulan elastis
p1.speedX += dot * nx;
p1.speedY += dot * ny;
p2.speedX -= dot * nx;
p2.speedY -= dot * ny;
// Geser partikel untuk mencegah overlap
const overlap = (p1.radius + p2.radius - distance) / 2;
p1.x -= overlap * nx;
p1.y -= overlap * ny;
p2.x += overlap * nx;
p2.y += overlap * ny;
}
}
}
}
// =============================================
// FUNGSI UTAMA
// =============================================
// Fungsi untuk inisialisasi simulasi
function init() {
// Buat 10 partikel awal
createParticles(10);
// Mulai loop animasi
animate();
}
// Fungsi untuk membuat sejumlah partikel baru
function createParticles(count) {
// Batasi jumlah partikel agar tidak melebihi maxParticles
if (particles.length + count > config.maxParticles) {
count = config.maxParticles - particles.length;
if (count <= 0) return;
}
// Tambahkan partikel baru ke array
for (let i = 0; i < count; i++) {
particles.push(new Particle());
}
}
// Fungsi untuk mereset simulasi
function resetSimulation() {
// Kosongkan array partikel
particles = [];
// Buat 5 partikel baru
createParticles(5);
}
// Fungsi animasi utama (loop rendering)
function animate() {
// Bersihkan canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Gambar latar belakang
ctx.fillStyle = colors.background;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Update posisi semua partikel
particles.forEach(particle => particle.update());
// Tangani tabrakan antar partikel
handleCollisions();
// Gambar semua partikel
particles.forEach(particle => particle.draw());
// Jadwalkan frame berikutnya
requestAnimationFrame(animate);
}
// =============================================
// EVENT LISTENERS
// =============================================
// Ambil elemen tombol
const addButton = document.getElementById('particleSimAdd');
const resetButton = document.getElementById('particleSimReset');
// Tambahkan listener untuk tombol "Tambah Partikel"
if (addButton) {
addButton.addEventListener('click', () => createParticles(3));
}
// Tambahkan listener untuk tombol "Reset"
if (resetButton) {
resetButton.addEventListener('click', resetSimulation);
}
// Tambahkan listener untuk klik pada canvas
canvas.addEventListener('click', (e) => {
// Hitung posisi klik relatif terhadap canvas
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// Tambahkan partikel baru di posisi klik jika belum mencapai batas
if (particles.length < config.maxParticles) {
particles.push(new Particle(x, y));
}
});
// =============================================
// MEMULAI SIMULASI
// =============================================
// Jalankan inisialisasi simulasi
init();
});
})();
</script>
</body>
</html>
Selamat mencoba, semoga berhasil.
Tidak ada komentar:
Posting Komentar