Double Slit

unsorted
Published

2025-04-15

Modified

2025-04-15

<div class="container">
    <h1>Wave Propagation Double Slit Experiment</h1>
    
    <div class="controls">
        <div class="control-group">
            <div class="control-title">Slit Configuration</div>
            <div class="slider-container">
                <label for="slitCount">Number of Slits: <span id="slitCountValue" class="slider-value">2</span></label>
                <input type="range" id="slitCount" min="1" max="10" value="2" step="1">
            </div>
            <div class="slider-container">
                <label for="slitWidth">Slit Width: <span id="slitWidthValue" class="slider-value">10</span> nm</label>
                <input type="range" id="slitWidth" min="5" max="50" value="10" step="1">
            </div>
            <div class="slider-container">
                <label for="slitSeparation">Slit Separation: <span id="slitSeparationValue" class="slider-value">100</span> nm</label>
                <input type="range" id="slitSeparation" min="30" max="300" value="100" step="10">
            </div>
        </div>
        
        <div class="control-group">
            <div class="control-title">Wave Properties</div>
            <div class="slider-container">
                <label for="wavelength">Wavelength: <span id="wavelengthValue" class="slider-value">500</span> nm</label>
                <input type="range" id="wavelength" min="400" max="700" value="500" step="5">
            </div>
            <div class="slider-container">
                <label for="animSpeed">Animation Speed: <span id="animSpeedValue" class="slider-value">5</span></label>
                <input type="range" id="animSpeed" min="1" max="20" value="5" step="1">
            </div>
            <div class="checkbox-container">
                <input type="checkbox" id="showWavefronts" checked>
                <label for="showWavefronts">Show Wavefronts</label>
            </div>
        </div>
    </div>
    
    <div class="button-container">
        <button id="animateBtn">Start Animation</button>
        <button id="resetBtn">Reset</button>
    </div>
    
    <canvas id="slitCanvas" width="800" height="600"></canvas>
    
    <div class="explanation">
        <h3>About the Double Slit Experiment</h3>
        <p>This visualization focuses on wave propagation in the double slit experiment. When light passes through narrow slits, it behaves as a wave, creating an interference pattern.</p>
        <p>In this simulation, you can see:</p>
        <ul>
            <li><strong>Wave propagation</strong> from each slit (semi-circular wavefronts)</li>
            <li><strong>Interference patterns</strong> where waves meet (bright areas show constructive interference, dark areas show destructive interference)</li>
            <li><strong>Intensity pattern</strong> at the top of the screen showing what would be observed on a detector</li>
        </ul>
        <p>Try changing the parameters to see how they affect the interference pattern:</p>
        <ul>
            <li><strong>Number of Slits:</strong> More slits create sharper, more complex patterns</li>
            <li><strong>Slit Width:</strong> Wider slits produce less pronounced diffraction effects</li>
            <li><strong>Slit Separation:</strong> Greater separation creates narrower fringes</li>
            <li><strong>Wavelength:</strong> Longer wavelengths create wider fringes</li>
        </ul>
    </div>
</div>

<script>
    // Canvas and context
    const canvas = document.getElementById('slitCanvas');
    const ctx = canvas.getContext('2d');
    
    // Controls
    const slitCountSlider = document.getElementById('slitCount');
    const slitWidthSlider = document.getElementById('slitWidth');
    const slitSeparationSlider = document.getElementById('slitSeparation');
    const wavelengthSlider = document.getElementById('wavelength');
    const animSpeedSlider = document.getElementById('animSpeed');
    const showWavefrontsCheckbox = document.getElementById('showWavefronts');
    const animateBtn = document.getElementById('animateBtn');
    const resetBtn = document.getElementById('resetBtn');
    
    // Value displays
    const slitCountValue = document.getElementById('slitCountValue');
    const slitWidthValue = document.getElementById('slitWidthValue');
    const slitSeparationValue = document.getElementById('slitSeparationValue');
    const wavelengthValue = document.getElementById('wavelengthValue');
    const animSpeedValue = document.getElementById('animSpeedValue');
    
    // Animation variables
    let animationId = null;
    let isAnimating = false;
    let time = 0;
    
    // Constants
    const SCREEN_DISTANCE = 500; // Distance from slits to top of screen
    const SLIT_Y_POSITION = canvas.height - 50; // Y position of slits
    const DETECTOR_Y_POSITION = 80; // Y position of detector (top of screen)
    const DETECTOR_HEIGHT = 60; // Height of the detector display
    
    // Helper function to get animation speed with exponential scaling
    function getScaledAnimSpeed() {
        const speedValue = parseInt(animSpeedSlider.value);
        return Math.pow(speedValue, 1.5) / 10;
    }
    
    // Initialize the canvas
    function initCanvas() {
        ctx.fillStyle = 'black';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        
        // Draw slits
        drawSlits();
        
        // Draw detector
        drawDetector();
        
        // Draw initial wave field
        drawWaveField();
    }
    
    // Draw the slits at the bottom of the canvas
    function drawSlits() {
        const slitCount = parseInt(slitCountSlider.value);
        const slitWidth = parseInt(slitWidthSlider.value);
        const slitSeparation = parseInt(slitSeparationSlider.value);
        
        // Clear the slit area
        ctx.fillStyle = 'black';
        ctx.fillRect(0, SLIT_Y_POSITION - 20, canvas.width, 50);
        
        // Draw barrier
        ctx.fillStyle = '#555';
        ctx.fillRect(0, SLIT_Y_POSITION, canvas.width, 10);
        
        // Calculate dynamic slit width and separation to ensure all slits remain visible
        const maxWidth = canvas.width * 0.8; // Use 80% of canvas width to ensure margins
        
        // Calculate scaled values to fit within available space
        const scaledSlitWidth = Math.min(slitWidth * 2, maxWidth / (slitCount * 2));
        let scaledSeparation = Math.min(slitSeparation * 2, maxWidth / (slitCount * 2));
        
        // Ensure minimum separation
        scaledSeparation = Math.max(10, scaledSeparation);
        
        // Calculate total width with scaled values
        const totalWidth = (slitCount * scaledSlitWidth) + ((slitCount - 1) * scaledSeparation);
        const startX = (canvas.width - totalWidth) / 2;
        
        // Draw slits
        ctx.fillStyle = 'white';
        for (let i = 0; i < slitCount; i++) {
            const slitX = startX + i * (scaledSlitWidth + scaledSeparation);
            ctx.fillRect(slitX, SLIT_Y_POSITION, scaledSlitWidth, 10);
        }
    }
    
    // Draw detector at top of screen
    function drawDetector() {
        // Draw detector background
        ctx.fillStyle = '#222';
        ctx.fillRect(0, DETECTOR_Y_POSITION - DETECTOR_HEIGHT, canvas.width, DETECTOR_HEIGHT);
        
        // Draw detector line
        ctx.fillStyle = '#444';
        ctx.fillRect(0, DETECTOR_Y_POSITION, canvas.width, 5);
        
        // Add label
        ctx.fillStyle = 'white';
        ctx.font = '12px Arial';
        ctx.textAlign = 'center';
        ctx.fillText('Detector', canvas.width / 2, DETECTOR_Y_POSITION - DETECTOR_HEIGHT + 15);
    }
    
    // Get slit positions based on current settings
    function getSlitPositions() {
        const slitCount = parseInt(slitCountSlider.value);
        const slitWidth = parseInt(slitWidthSlider.value);
        const slitSeparation = parseInt(slitSeparationSlider.value);
        
        // Calculate dynamic slit width and separation to ensure all slits remain visible
        const maxWidth = canvas.width * 0.8;
        const scaledSlitWidth = Math.min(slitWidth * 2, maxWidth / (slitCount * 2));
        let scaledSeparation = Math.min(slitSeparation * 2, maxWidth / (slitCount * 2));
        scaledSeparation = Math.max(10, scaledSeparation);
        
        // Calculate total width with scaled values
        const totalWidth = (slitCount * scaledSlitWidth) + ((slitCount - 1) * scaledSeparation);
        const startX = (canvas.width - totalWidth) / 2;
        
        // Generate slit positions
        const slitPositions = [];
        for (let i = 0; i < slitCount; i++) {
            const slitCenterX = startX + (i * (scaledSlitWidth + scaledSeparation)) + (scaledSlitWidth / 2);
            slitPositions.push({
                x: slitCenterX,
                y: SLIT_Y_POSITION,
                width: scaledSlitWidth
            });
        }
        
        return slitPositions;
    }
    
    // Calculate wave amplitude at a given point
    function calculateWaveAmplitude(x, y, animTime) {
        const slitPositions = getSlitPositions();
        const wavelength = parseInt(wavelengthSlider.value);
        const scaledWavelength = wavelength / 15; // Scale for visual appeal
        
        let totalAmplitude = 0;
        
        // Calculate contribution from each slit
        for (const slit of slitPositions) {
            // Calculate distance from slit to point
            const dx = x - slit.x;
            const dy = y - slit.y;
            const distance = Math.sqrt(dx*dx + dy*dy);
            
            // Calculate phase based on distance and time
            const phase = (distance % scaledWavelength) / scaledWavelength;
            
            // For wider slits, calculate multiple point sources across the slit
            const numPointSources = Math.max(1, Math.floor(slit.width / 10));
            let slitAmplitude = 0;
            
            for (let i = 0; i < numPointSources; i++) {
                const sourceX = slit.x - (slit.width / 2) + (slit.width * (i + 0.5) / numPointSources);
                const sourceDx = x - sourceX;
                const sourceDy = y - slit.y;
                const sourceDistance = Math.sqrt(sourceDx*sourceDx + sourceDy*sourceDy);
                
                // Only contribute if point is in front of the slit (y < slit.y)
                if (y < slit.y) {
                    // Calculate wave amplitude with distance falloff
                    const distanceFalloff = 1 / Math.sqrt(sourceDistance + 1);
                    const wavePhase = ((sourceDistance / scaledWavelength) * 2 * Math.PI) - (animTime * 0.2);
                    slitAmplitude += Math.cos(wavePhase) * distanceFalloff;
                }
            }
            
            // Average the amplitude from all point sources in this slit
            slitAmplitude /= numPointSources;
            totalAmplitude += slitAmplitude;
        }
        
        return totalAmplitude;
    }
    
    // Draw wave field and interference pattern
    function drawWaveField() {
        const showWavefronts = showWavefrontsCheckbox.checked;
        const scaledSpeed = getScaledAnimSpeed();
        const wavelength = parseInt(wavelengthSlider.value);
        const slitPositions = getSlitPositions();
        const animTime = time * scaledSpeed;
        
        // Clear the wave area
        ctx.fillStyle = 'black';
        ctx.fillRect(0, DETECTOR_Y_POSITION, canvas.width, SLIT_Y_POSITION - DETECTOR_Y_POSITION);
        
        // Get wave color based on wavelength
        const waveColor = getWavelengthColor(wavelength);
        
        // Draw the wave field
        if (showWavefronts) {
            drawWavefronts(slitPositions, wavelength, waveColor, animTime);
        } else {
            // Draw the interference pattern using a pixel-based approach
            const resolution = 3; // Can adjust for performance vs. quality
            const imageData = ctx.createImageData(canvas.width, SLIT_Y_POSITION - DETECTOR_Y_POSITION);
            
            for (let y = 0; y < SLIT_Y_POSITION - DETECTOR_Y_POSITION; y += resolution) {
                for (let x = 0; x < canvas.width; x += resolution) {
                    const canvasY = DETECTOR_Y_POSITION + y;
                    
                    // Calculate wave amplitude at this point
                    const amplitude = calculateWaveAmplitude(x, canvasY, animTime);
                    
                    // Convert amplitude to intensity
                    const intensity = Math.pow(amplitude, 2) * 255;
                    
                    // Set pixel color based on wavelength and intensity
                    const r = Math.min(255, Math.max(0, waveColor.r * intensity / 255));
                    const g = Math.min(255, Math.max(0, waveColor.g * intensity / 255));
                    const b = Math.min(255, Math.max(0, waveColor.b * intensity / 255));
                    
                    // Fill the resolution-sized block with this color
                    for (let dy = 0; dy < resolution && y + dy < SLIT_Y_POSITION - DETECTOR_Y_POSITION; dy++) {
                        for (let dx = 0; dx < resolution && x + dx < canvas.width; dx++) {
                            const pixelIndex = ((y + dy) * canvas.width + (x + dx)) * 4;
                            
                            if (pixelIndex < imageData.data.length - 3) {
                                imageData.data[pixelIndex] = r;
                                imageData.data[pixelIndex + 1] = g;
                                imageData.data[pixelIndex + 2] = b;
                                imageData.data[pixelIndex + 3] = 255;
                            }
                        }
                    }
                }
            }
            
            ctx.putImageData(imageData, 0, DETECTOR_Y_POSITION);
        }
        
        // Draw the intensity profile at the detector
        drawDetectorPattern(waveColor, animTime);
    }
    
    // Draw wavefronts from slits
    function drawWavefronts(slitPositions, wavelength, waveColor, animTime) {
        const scaledWavelength = wavelength / 15; // Scale wavelength for visual appeal
        
        // For each slit, draw concentric semicircles representing wavefronts
        for (const slit of slitPositions) {
            // Calculate multiple point sources for wide slits
            const numPointSources = Math.max(1, Math.floor(slit.width / 10));
            
            for (let i = 0; i < numPointSources; i++) {
                const sourceX = slit.x - (slit.width / 2) + (slit.width * (i + 0.5) / numPointSources);
                const maxRadius = SLIT_Y_POSITION - DETECTOR_Y_POSITION;
                
                // Draw multiple wavefronts
                for (let radius = (animTime % scaledWavelength); radius < maxRadius; radius += scaledWavelength) {
                    // Calculate opacity based on distance
                    const opacity = Math.max(0.05, 1 - (radius / maxRadius) * 0.9);
                    
                    ctx.beginPath();
                    ctx.arc(sourceX, SLIT_Y_POSITION, radius, Math.PI, 0, false);
                    ctx.strokeStyle = `rgba(${waveColor.r}, ${waveColor.g}, ${waveColor.b}, ${opacity})`;
                    ctx.lineWidth = 1;
                    ctx.stroke();
                }
            }
        }
        
        // Now draw the interference pattern
        const resolution = 4; // Resolution of interference visualization
        
        for (let y = DETECTOR_Y_POSITION + resolution; y < SLIT_Y_POSITION - resolution; y += resolution) {
            for (let x = resolution; x < canvas.width - resolution; x += resolution) {
                // Calculate wave amplitude at this point
                const amplitude = calculateWaveAmplitude(x, y, animTime);
                const intensity = Math.pow(amplitude, 2);
                
                // Only draw points with notable intensity
                if (intensity > 0.15) {
                    const size = Math.min(resolution - 1, intensity * resolution * 1.5);
                    const opacity = Math.min(1, intensity * 2);
                    
                    ctx.fillStyle = `rgba(${waveColor.r}, ${waveColor.g}, ${waveColor.b}, ${opacity})`;
                    ctx.fillRect(x - size/2, y - size/2, size, size);
                }
            }
        }
    }
    
    // Draw the intensity pattern on the detector
    function drawDetectorPattern(waveColor, animTime) {
        // Clear the detector pattern area
        ctx.fillStyle = '#222';
        ctx.fillRect(0, DETECTOR_Y_POSITION - DETECTOR_HEIGHT, canvas.width, DETECTOR_HEIGHT);
        
        // Draw grid lines for reference
        ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
        ctx.lineWidth = 1;
        
        // Horizontal grid lines
        for (let y = 0; y < DETECTOR_HEIGHT; y += 10) {
            ctx.beginPath();
            ctx.moveTo(0, DETECTOR_Y_POSITION - y);
            ctx.lineTo(canvas.width, DETECTOR_Y_POSITION - y);
            ctx.stroke();
        }
        
        // Calculate the intensity profile
        const intensities = [];
        for (let x = 0; x < canvas.width; x += 2) {
            const amplitude = calculateWaveAmplitude(x, DETECTOR_Y_POSITION + 5, animTime);
            const intensity = Math.pow(amplitude, 2);
            intensities.push(intensity);
        }
        
        // Find the maximum intensity for scaling
        const maxIntensity = Math.max(...intensities, 0.1);
        
        // Fill the area under the curve
        ctx.beginPath();
        ctx.moveTo(0, DETECTOR_Y_POSITION);
        
        for (let i = 0; i < intensities.length; i++) {
            const x = i * 2;
            const scaledIntensity = (intensities[i] / maxIntensity) * (DETECTOR_HEIGHT - 10);
            ctx.lineTo(x, DETECTOR_Y_POSITION - scaledIntensity);
        }
        
        ctx.lineTo(canvas.width, DETECTOR_Y_POSITION);
        ctx.closePath();
        
        // Create a gradient fill matching the wave color
        const gradient = ctx.createLinearGradient(0, DETECTOR_Y_POSITION - DETECTOR_HEIGHT, 0, DETECTOR_Y_POSITION);
        gradient.addColorStop(0, `rgba(${waveColor.r}, ${waveColor.g}, ${waveColor.b}, 0.8)`);
        gradient.addColorStop(1, `rgba(${waveColor.r}, ${waveColor.g}, ${waveColor.b}, 0.1)`);
        ctx.fillStyle = gradient;
        ctx.fill();
        
        // Draw the outline
        ctx.strokeStyle = `rgb(${waveColor.r}, ${waveColor.g}, ${waveColor.b})`;
        ctx.lineWidth = 2;
        ctx.beginPath();
        
        for (let i = 0; i < intensities.length; i++) {
            const x = i * 2;
            const scaledIntensity = (intensities[i] / maxIntensity) * (DETECTOR_HEIGHT - 10);
            
            if (i === 0) {
                ctx.moveTo(x, DETECTOR_Y_POSITION - scaledIntensity);
            } else {
                ctx.lineTo(x, DETECTOR_Y_POSITION - scaledIntensity);
            }
        }
        
        ctx.stroke();
        
        // Add detector label
        ctx.fillStyle = 'white';
        ctx.font = '12px Arial';
        ctx.textAlign = 'center';
        ctx.fillText('Detector', canvas.width / 2, DETECTOR_Y_POSITION - DETECTOR_HEIGHT + 15);
    }
    
    // Helper function to convert wavelength to RGB color
    function getWavelengthColor(wavelength) {
        let r, g, b;
        
        // Simple wavelength to RGB approximation
        if (wavelength < 480) {
            // Blue to violet
            r = Math.floor(255 * (wavelength - 400) / 80);
            g = 0;
            b = 255;
        } else if (wavelength < 510) {
            // Blue to green
            r = 0;
            g = Math.floor(255 * (wavelength - 480) / 30);
            b = Math.floor(255 * (510 - wavelength) / 30);
        } else if (wavelength < 580) {
            // Green to yellow
            r = Math.floor(255 * (wavelength - 510) / 70);
            g = 255;
            b = 0;
        } else if (wavelength < 645) {
            // Yellow to red
            r = 255;
            g = Math.floor(255 * (645 - wavelength) / 65);
            b = 0;
        } else {
            // Red
            r = 255;
            g = 0;
            b = 0;
        }
        
        return { r, g, b };
    }
    
    // Animation function
    function animate() {
        // Get the scaled animation speed
        const scaledSpeed = getScaledAnimSpeed();
        
        // Update the time
        time += 0.1 * scaledSpeed;
        
        // Draw the wave field
        drawWaveField();
        
        // Request the next animation frame
        animationId = requestAnimationFrame(animate);
    }
    
    // Update value displays
    function updateValueDisplays() {
        slitCountValue.textContent = slitCountSlider.value;
        slitWidthValue.textContent = slitWidthSlider.value;
        slitSeparationValue.textContent = slitSeparationSlider.value;
        wavelengthValue.textContent = wavelengthSlider.value;
        animSpeedValue.textContent = animSpeedSlider.value;
    }
    
    // Event listeners
    slitCountSlider.addEventListener('input', () => {
        updateValueDisplays();
        drawSlits();
        drawWaveField();
    });
    
    slitWidthSlider.addEventListener('input', () => {
        updateValueDisplays();
        drawSlits();
        drawWaveField();
    });
    
    slitSeparationSlider.addEventListener('input', () => {
        updateValueDisplays();
        drawSlits();
        drawWaveField();
    });
    
    wavelengthSlider.addEventListener('input', () => {
        updateValueDisplays();
        drawWaveField();
    });
    
    animSpeedSlider.addEventListener('input', () => {
        updateValueDisplays();
        // No need to redraw since speed only affects animation
    });
    
    showWavefrontsCheckbox.addEventListener('change', drawWaveField);
    
    animateBtn.addEventListener('click', () => {
        if (isAnimating) {
            cancelAnimationFrame(animationId);
            animateBtn.textContent = 'Start Animation';
            isAnimating = false;
        } else {
            animate();
            animateBtn.textContent = 'Stop Animation';
            isAnimating = true;
        }
    });
    
    resetBtn.addEventListener('click', () => {
        // Reset sliders to default values
        slitCountSlider.value = 2;
        slitWidthSlider.value = 10;
        slitSeparationSlider.value = 100;
        wavelengthSlider.value = 500;
        animSpeedSlider.value = 5;
        showWavefrontsCheckbox.checked = true;
        
        // Stop animation if running
        if (isAnimating) {
            cancelAnimationFrame(animationId);
            animateBtn.textContent = 'Start Animation';
            isAnimating = false;
        }
        
        // Reset time
        time = 0;
        
        // Update displays
        updateValueDisplays();
        
        // Redraw
        initCanvas();
    });
    
    // Initialize the visualization
    updateValueDisplays();
    initCanvas();
</script>