Double Slit
unsorted
<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>