Cara Membuat Simulasi Tumbukan Antarpartikel dengan HTML+Canvas

Jumat, 16 Mei 2025 edit

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.

Bagikan di

Tidak ada komentar:

Posting Komentar

 
Copyright © 2015-2025 Urip dot Info | Disain Template oleh Herdiansyah Dimodivikasi Urip.Info