Enjoy this little interactive simulation of molecule diffusion between two fluids. Use the sliders to control the parameters.

The fluid on the left is heavier. The molecules move in brownian motion and bounce off of each other.

It may not be very realistic, but it's fun to play with.

Full JavaScript source is below.



<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewportcontent="width=device-widthinitial-scale=1.0">
    <title>Molecular Fluid Mixing Simulation</title>
    <style>
        body {
            backgroundblack;
            colorwhite;
            displayflex;
            flex-directioncolumn;
            align-itemscenter;
        }
        
        .controls {
            margin-top: 20px;
            displayflex;
            gap: 15px;
        }
        
        .slider-container {
            displayflex;
            flex-directioncolumn;
            align-itemscenter;
        }
    </style>
</head>
<body>
    <div class="container">
        <canvas id="canvaswidth="800height="600"></canvas>
        
        <div class="controls">
            <button id="resetBtn">Reset Simulation</button>
            <button id="pauseBtn">Pause</button>
            
            <div class="slider-container">
                <label for="speedSlider">Molecular Speed</label>
                <input type="rangeid="speedSlidermin="0.1max="3step="0.1value="1">
            </div>
            
            <div class="slider-container">
                <label for="densitySlider">Molecule Density</label>
                <input type="rangeid="densitySlidermin="100max="1000step="50value="400">
            </div>
            
            <div class="slider-container">
                <label for="diffusionSlider">Diffusion Rate</label>
                <input type="rangeid="diffusionSlidermin="0.1max="2step="0.1value="0.8">
            </div>
        </div>
    </div>
 
    <script>
        class Molecule {
            constructor(xytypecanvas) {
                this.x = x;
                this.y = y;
                this.vx = (Math.random() - 0.5) * 4;
                this.vy = (Math.random() - 0.5) * 4;
                this.type = type// 'A' or 'B'
                this.radius = type === 'A' ? 3 : 4;
                this.mass = type === 'A' ? 1 : 1.5;
                this.color = type === 'A' ? '#ff6b6b' : '#4ecdc4';
                this.canvas = canvas;
                this.trail = [];
                this.maxTrailLength = 10;
            }
            
            update(speedMultiplierdiffusionRate) {
                // Add random brownian motion
                this.vx += (Math.random() - 0.5) * 0.5 * diffusionRate;
                this.vy += (Math.random() - 0.5) * 0.5 * diffusionRate;
                
                // Apply friction
                this.vx *= 0.98;
                this.vy *= 0.98;
                
                // Limit velocity
                const maxVel = 3 * speedMultiplier;
                const vel = Math.sqrt(this.vx * this.vx + this.vy * this.vy);
                if (vel > maxVel) {
                    this.vx = (this.vx / vel) * maxVel;
                    this.vy = (this.vy / vel) * maxVel;
                }
                
                // Update position
                this.x += this.vx * speedMultiplier;
                this.y += this.vy * speedMultiplier;
                
                // Boundary collision
                if (this.x <= this.radius || this.x >= this.canvas.width - this.radius) {
                    this.vx *= -0.8;
                    this.x = Math.max(this.radiusMath.min(this.canvas.width - this.radiusthis.x));
                }
                if (this.y <= this.radius || this.y >= this.canvas.height - this.radius) {
                    this.vy *= -0.8;
                    this.y = Math.max(this.radiusMath.min(this.canvas.height - this.radiusthis.y));
                }
                
                // Update trail
                this.trail.push({xthis.xythis.y});
                if (this.trail.length > this.maxTrailLength) {
                    this.trail.shift();
                }
            }
            
            draw(ctx) {
                // Draw trail
                ctx.strokeStyle = this.color;
                ctx.lineWidth = 1;
                ctx.globalAlpha = 0.3;
                ctx.beginPath();
                for (let i = 1i < this.trail.lengthi++) {
                    ctx.moveTo(this.trail[i-1].xthis.trail[i-1].y);
                    ctx.lineTo(this.trail[i].xthis.trail[i].y);
                }
                ctx.stroke();
                
                // Draw molecule
                ctx.globalAlpha = 0.8;
                const gradient = ctx.createRadialGradient(
                    this.x - this.radius/3this.y - this.radius/30,
                    this.xthis.ythis.radius
                );
                gradient.addColorStop(0this.color);
                gradient.addColorStop(1this.color + '80');
                
                ctx.fillStyle = gradient;
                ctx.beginPath();
                ctx.arc(this.xthis.ythis.radius0Math.PI * 2);
                ctx.fill();
                
                // Add shine effect
                ctx.globalAlpha = 0.6;
                ctx.fillStyle = 'white';
                ctx.beginPath();
                ctx.arc(this.x - this.radius/3this.y - this.radius/3this.radius/30Math.PI * 2);
                ctx.fill();
                
                ctx.globalAlpha = 1;
            }
            
            checkCollision(other) {
                const dx = this.x - other.x;
                const dy = this.y - other.y;
                const distance = Math.sqrt(dx * dx + dy * dy);
                const minDistance = this.radius + other.radius;
                
                if (distance < minDistance) {
                    // Collision response
                    const angle = Math.atan2(dydx);
                    const sin = Math.sin(angle);
                    const cos = Math.cos(angle);
                    
                    // Rotate velocities
                    const vx1 = this.vx * cos + this.vy * sin;
                    const vy1 = this.vy * cos - this.vx * sin;
                    const vx2 = other.vx * cos + other.vy * sin;
                    const vy2 = other.vy * cos - other.vx * sin;
                    
                    // Conservation of momentum
                    const finalVx1 = ((this.mass - other.mass) * vx1 + 2 * other.mass * vx2) / (this.mass + other.mass);
                    const finalVx2 = ((other.mass - this.mass) * vx2 + 2 * this.mass * vx1) / (this.mass + other.mass);
                    
                    // Rotate velocities back
                    this.vx = finalVx1 * cos - vy1 * sin;
                    this.vy = vy1 * cos + finalVx1 * sin;
                    other.vx = finalVx2 * cos - vy2 * sin;
                    other.vy = vy2 * cos + finalVx2 * sin;
                    
                    // Separate molecules
                    const overlap = minDistance - distance;
                    const separationX = (dx / distance) * overlap * 0.5;
                    const separationY = (dy / distance) * overlap * 0.5;
                    
                    this.x += separationX;
                    this.y += separationY;
                    other.x -= separationX;
                    other.y -= separationY;
                }
            }
        }
        
        class FluidSimulation {
            constructor(canvasId) {
                this.canvas = document.getElementById(canvasId);
                this.ctx = this.canvas.getContext('2d');
                this.molecules = [];
                this.animationId = null;
                this.isPaused = false;
                this.speedMultiplier = 1;
                this.diffusionRate = 0.8;
                
                this.initMolecules(400);
                this.bindEvents();
                this.animate();
            }
            
            initMolecules(count) {
                this.molecules = [];
                const halfWidth = this.canvas.width / 2;
                
                // Create fluid A molecules (left side)
                for (let i = 0i < count / 2i++) {
                    const x = Math.random() * (halfWidth - 50) + 25;
                    const y = Math.random() * (this.canvas.height - 50) + 25;
                    this.molecules.push(new Molecule(xy, 'A', this.canvas));
                }
                
                // Create fluid B molecules (right side)
                for (let i = 0i < count / 2i++) {
                    const x = Math.random() * (halfWidth - 50) + halfWidth + 25;
                    const y = Math.random() * (this.canvas.height - 50) + 25;
                    this.molecules.push(new Molecule(xy, 'B', this.canvas));
                }
            }
            
            animate() {
                if (!this.isPaused) {
                    this.update();
                    this.draw();
                }
                this.animationId = requestAnimationFrame(() => this.animate());
            }
            
            update() {
                // Update molecules
                for (let molecule of this.molecules) {
                    molecule.update(this.speedMultiplierthis.diffusionRate);
                }
                
                // Check collisions
                for (let i = 0i < this.molecules.lengthi++) {
                    for (let j = i + 1j < this.molecules.lengthj++) {
                        this.molecules[i].checkCollision(this.molecules[j]);
                    }
                }
            }
            
            draw() {
                // Clear canvas with fade effect
                this.ctx.fillStyle = 'rgba(0000.1)';
                this.ctx.fillRect(00this.canvas.widththis.canvas.height);
                
                // Draw dividing line (fades over time)
                this.ctx.strokeStyle = 'rgba(2552552550.1)';
                this.ctx.lineWidth = 2;
                this.ctx.setLineDash([55]);
                this.ctx.beginPath();
                this.ctx.moveTo(this.canvas.width / 20);
                this.ctx.lineTo(this.canvas.width / 2this.canvas.height);
                this.ctx.stroke();
                this.ctx.setLineDash([]);
                
                // Draw molecules
                for (let molecule of this.molecules) {
                    molecule.draw(this.ctx);
                }
            }
            
            bindEvents() {
                document.getElementById('resetBtn').addEventListener('click', () => {
                    this.initMolecules(parseInt(document.getElementById('densitySlider').value));
                });
                
                document.getElementById('pauseBtn').addEventListener('click', (e) => {
                    this.isPaused = !this.isPaused;
                    e.target.textContent = this.isPaused ? 'Resume' : 'Pause';
                });
                
                document.getElementById('speedSlider').addEventListener('input', (e) => {
                    this.speedMultiplier = parseFloat(e.target.value);
                });
                
                document.getElementById('densitySlider').addEventListener('input', (e) => {
                    this.initMolecules(parseInt(e.target.value));
                });
                
                document.getElementById('diffusionSlider').addEventListener('input', (e) => {
                    this.diffusionRate = parseFloat(e.target.value);
                });
            }
        }
        
        // Initialize simulation when page loads
        window.addEventListener('load', () => {
            new FluidSimulation('canvas');
        });
    </script>
</body>
</html>


Check out these programming tutorials:

JavaScript:

Optical illusion (18 lines)

Spinning squares - visual effect (25 lines)

Oldschool fire effect (20 lines)

Fireworks (60 lines)

Animated fractal (32 lines)

Physics engine for beginners

Physics engine - interactive sandbox

Physics engine - silly contraption

Starfield (21 lines)

Yin Yang with a twist (4 circles and 20 lines)

Tile map editor (70 lines)

Sine scroller (30 lines)

Interactive animated sprites

Image transition effect (16 lines)

Your first program in JavaScript: you need 5 minutes and a notepad


Fractals in Excel