光的折射实验模拟器HTML代码
一个简单的光线折射模拟应用。该应用将允许用户调整入射角,并显示光线穿过玻璃砖后的折射路径。
物理原理:
斯涅尔定律(折射定律):n1 * sin(θ1) = n2 * sin(θ2)
其中,n1和n2分别是两种介质的折射率,θ1是入射角,θ2是折射角。
假设空气的折射率n1=1,玻璃的折射率n2=1.5。
光线从空气进入玻璃时,发生第一次折射(入射角θ1,折射角θ2),然后在玻璃中传播到另一面,再次从玻璃进入空气,发生第二次折射(此时入射角为θ2,折射角又变回θ1,因为对称性)。

以下是实现代码( 由 deepseek 依据人工指令生成):
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>光的折射实验模拟器</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Arial', sans-serif;
}
body {
background-color: #f5f7fa;
color: #333;
line-height: 1.6;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background-color: white;
border-radius: 12px;
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
padding: 25px;
}
h1 {
text-align: center;
color: #2c3e50;
margin-bottom: 20px;
}
.description {
text-align: center;
margin-bottom: 25px;
color: #555;
}
.simulation-area {
display: flex;
flex-wrap: wrap;
gap: 25px;
margin-bottom: 25px;
}
.canvas-container {
flex: 2;
min-width: 600px;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
background-color: #f9f9f9;
position: relative;
}
#refractionCanvas {
width: 100%;
height: 500px;
display: block;
}
.controls {
flex: 1;
min-width: 300px;
padding: 20px;
background-color: #f8f9fa;
border-radius: 8px;
}
.control-group {
margin-bottom: 20px;
}
h2 {
color: #3498db;
margin-bottom: 15px;
font-size: 1.3rem;
}
label {
display: block;
margin-bottom: 8px;
font-weight: bold;
color: #555;
}
input[type="range"] {
width: 100%;
margin-bottom: 10px;
}
.value-display {
display: flex;
justify-content: space-between;
font-size: 0.9rem;
color: #666;
}
.info-panel {
background-color: #e8f4fc;
padding: 20px;
border-radius: 8px;
margin-bottom: 25px;
}
.info-panel h2 {
color: #2980b9;
margin-bottom: 15px;
}
.law-explanation {
margin-bottom: 15px;
}
.formula {
font-family: 'Courier New', monospace;
background-color: #f1f1f1;
padding: 8px 12px;
border-radius: 4px;
margin: 10px 0;
display: inline-block;
}
.button {
background-color: #3498db;
color: white;
border: none;
padding: 10px 15px;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.3s;
margin-right: 10px;
margin-bottom: 10px;
}
.button:hover {
background-color: #2980b9;
}
.button.reset {
background-color: #e74c3c;
}
.button.reset:hover {
background-color: #c0392b;
}
.measurements {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-top: 20px;
}
.measurement-box {
background-color: white;
padding: 15px;
border-radius: 6px;
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
text-align: center;
}
.measurement-value {
font-size: 1.5rem;
font-weight: bold;
color: #3498db;
margin-top: 5px;
}
@media (max-width: 1024px) {
.simulation-area {
flex-direction: column;
}
.canvas-container, .controls {
width: 100%;
}
.canvas-container {
min-width: 100%;
}
}
</style>
</head>
<body>
<div>
<h1>光的折射实验模拟器</h1>
<p>通过调整参数观察光线通过玻璃砖时的折射现象,理解折射定律</p>
<div>
<div>
<canvas id="refractionCanvas"></canvas>
</div>
<div>
<div>
<h2>光线参数</h2>
<label for="incidentAngle">入射角 (°)</label>
<input type="range" id="incidentAngle" min="0" max="80" value="30" step="1">
<div>
<span>0°</span>
<span id="incidentAngleValue">30°</span>
<span>80°</span>
</div>
<label for="wavelength">光的波长 (nm)</label>
<input type="range" id="wavelength" min="380" max="780" value="550" step="10">
<div>
<span>380nm</span>
<span id="wavelengthValue">550nm</span>
<span>780nm</span>
</div>
</div>
<div>
<h2>介质参数</h2>
<label for="glassRefractiveIndex">玻璃折射率</label>
<input type="range" id="glassRefractiveIndex" min="1.4" max="1.8" value="1.5" step="0.1">
<div>
<span>1.4</span>
<span id="glassRefractiveIndexValue">1.5</span>
<span>1.8</span>
</div>
<label for="glassWidth">玻璃砖宽度</label>
<input type="range" id="glassWidth" min="150" max="400" value="250" step="10">
<div>
<span>窄</span>
<span id="glassWidthValue">中等</span>
<span>宽</span>
</div>
<label for="glassThickness">玻璃砖厚度</label>
<input type="range" id="glassThickness" min="80" max="200" value="120" step="10">
<div>
<span>薄</span>
<span id="glassThicknessValue">中等</span>
<span>厚</span>
</div>
</div>
<div>
<h2>操作</h2>
<button id="toggleAnimation">暂停/播放动画</button>
<button class="button reset" id="resetButton">重置参数</button>
</div>
</div>
</div>
<div>
<h2>折射定律 (斯涅尔定律)</h2>
<div>
<p>当光从一种介质进入另一种介质时,光的传播方向会发生改变,这种现象称为折射。</p>
<p>n₁ × sin(θ₁) = n₂ × sin(θ₂)</p>
<p>其中:</p>
<ul>
<li>n₁ 和 n₂ 分别是两种介质的折射率</li>
<li>θ₁ 是入射角(入射光线与法线的夹角)</li>
<li>θ₂ 是折射角(折射光线与法线的夹角)</li>
</ul>
</div>
<div>
<div>
<div>入射角</div>
<div id="incidentAngleDisplay">30°</div>
</div>
<div>
<div>折射角</div>
<div id="refractionAngleDisplay">19.47°</div>
</div>
<div>
<div>出射角</div>
<div id="exitAngleDisplay">30°</div>
</div>
<div>
<div>侧向位移</div>
<div id="lateralDisplacementDisplay">16.1px</div>
</div>
</div>
</div>
</div>
<script>
// 获取DOM元素
const canvas = document.getElementById('refractionCanvas');
const ctx = canvas.getContext('2d');
const incidentAngleSlider = document.getElementById('incidentAngle');
const wavelengthSlider = document.getElementById('wavelength');
const glassRefractiveIndexSlider = document.getElementById('glassRefractiveIndex');
const glassWidthSlider = document.getElementById('glassWidth');
const glassThicknessSlider = document.getElementById('glassThickness');
const incidentAngleValue = document.getElementById('incidentAngleValue');
const wavelengthValue = document.getElementById('wavelengthValue');
const glassRefractiveIndexValue = document.getElementById('glassRefractiveIndexValue');
const glassWidthValue = document.getElementById('glassWidthValue');
const glassThicknessValue = document.getElementById('glassThicknessValue');
const incidentAngleDisplay = document.getElementById('incidentAngleDisplay');
const refractionAngleDisplay = document.getElementById('refractionAngleDisplay');
const exitAngleDisplay = document.getElementById('exitAngleDisplay');
const lateralDisplacementDisplay = document.getElementById('lateralDisplacementDisplay');
const toggleAnimationButton = document.getElementById('toggleAnimation');
const resetButton = document.getElementById('resetButton');
// 初始化变量
let incidentAngle = 30; // 入射角(度)
let wavelength = 550; // 波长(nm)
let glassRefractiveIndex = 1.5; // 玻璃折射率
let glassWidth = 250; // 玻璃宽度(像素)
let glassThickness = 120; // 玻璃厚度(像素)
let airRefractiveIndex = 1.0; // 空气折射率
let isAnimating = true;
let animationFrameId;
// 设置Canvas尺寸
function resizeCanvas() {
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
draw();
}
// 绘制场景
function draw() {
const width = canvas.width;
const height = canvas.height;
// 清除画布
ctx.clearRect(0, 0, width, height);
// 绘制背景
ctx.fillStyle = '#e8f4fc';
ctx.fillRect(0, 0, width, height);
// 计算玻璃砖位置 - 向右偏移,为左侧留出空间
const glassX = width * 0.5;
const glassY = height / 2 - glassThickness / 2;
// 绘制玻璃砖
ctx.fillStyle = 'rgba(173, 216, 230, 0.5)';
ctx.fillRect(glassX, glassY, glassWidth, glassThickness);
ctx.strokeStyle = '#4682b4';
ctx.lineWidth = 2;
ctx.strokeRect(glassX, glassY, glassWidth, glassThickness);
// 计算光线路径 - 从玻璃砖上方开始
const rayStartX = glassX + glassWidth / 2;
const rayStartY = glassY - 80; // 从玻璃砖上方80像素处开始
// 入射光线
const incidentAngleRad = incidentAngle * Math.PI / 180;
// 计算入射点(玻璃上表面)
const entryX = rayStartX + Math.tan(incidentAngleRad) * (glassY - rayStartY);
const entryY = glassY;
// 绘制入射光线
drawRay(rayStartX, rayStartY, entryX, entryY, wavelengthToColor(wavelength));
// 计算折射角
const refractionAngleRad = Math.asin(
(airRefractiveIndex * Math.sin(incidentAngleRad)) / glassRefractiveIndex
);
const refractionAngle = refractionAngleRad * 180 / Math.PI;
// 玻璃内部光线
const internalRayLength = glassThickness / Math.cos(refractionAngleRad);
const internalRayEndX = entryX + Math.sin(refractionAngleRad) * internalRayLength;
const internalRayEndY = entryY + Math.cos(refractionAngleRad) * internalRayLength;
// 绘制玻璃内部光线
drawRay(entryX, entryY, internalRayEndX, internalRayEndY, wavelengthToColor(wavelength), true);
// 计算出射角(等于入射角)
const exitAngleRad = Math.asin(
(glassRefractiveIndex * Math.sin(refractionAngleRad)) / airRefractiveIndex
);
const exitAngle = exitAngleRad * 180 / Math.PI;
// 出射光线
const exitRayLength = 100;
const exitRayEndX = internalRayEndX + Math.sin(exitAngleRad) * exitRayLength;
const exitRayEndY = internalRayEndY + Math.cos(exitAngleRad) * exitRayLength;
// 绘制出射光线
drawRay(internalRayEndX, internalRayEndY, exitRayEndX, exitRayEndY, wavelengthToColor(wavelength));
// 绘制法线(延长到玻璃砖上方和下方)
drawExtendedNormal(entryX, entryY, glassThickness, rayStartY);
drawExtendedNormal(internalRayEndX, internalRayEndY, glassThickness, rayStartY);
// 绘制角度标注
drawAngleMarkers(entryX, entryY, internalRayEndX, internalRayEndY, incidentAngle, refractionAngle, exitAngle);
// 计算并显示侧向位移
const lateralDisplacement = Math.abs(exitRayEndX - rayStartX);
// 更新显示值
incidentAngleDisplay.textContent = incidentAngle.toFixed(2) + '°';
refractionAngleDisplay.textContent = refractionAngle.toFixed(2) + '°';
exitAngleDisplay.textContent = exitAngle.toFixed(2) + '°';
lateralDisplacementDisplay.textContent = lateralDisplacement.toFixed(1) + 'px';
}
// 绘制光线
function drawRay(startX, startY, endX, endY, color, isDashed = false) {
ctx.beginPath();
ctx.moveTo(startX, startY);
ctx.lineTo(endX, endY);
ctx.strokeStyle = color;
ctx.lineWidth = 2;
if (isDashed) {
ctx.setLineDash([5, 5]);
} else {
ctx.setLineDash([]);
}
ctx.stroke();
ctx.setLineDash([]);
// 绘制箭头
drawArrow(endX, endY, Math.atan2(endY - startY, endX - startX));
}
// 绘制箭头
function drawArrow(x, y, angle) {
const arrowLength = 10;
const arrowWidth = 5;
ctx.save();
ctx.translate(x, y);
ctx.rotate(angle);
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(-arrowLength, -arrowWidth);
ctx.moveTo(0, 0);
ctx.lineTo(-arrowLength, arrowWidth);
ctx.stroke();
ctx.restore();
}
// 绘制延长的法线
function drawExtendedNormal(x, y, glassThickness, rayStartY) {
const extendLength = 50; // 延长50像素
ctx.beginPath();
ctx.moveTo(x, y - extendLength); // 延长到玻璃砖上方
ctx.lineTo(x, y + glassThickness + extendLength); // 延长到玻璃砖下方
ctx.strokeStyle = '#888';
ctx.lineWidth = 1;
ctx.setLineDash([2, 2]);
ctx.stroke();
ctx.setLineDash([]);
}
// 绘制角度标注
function drawAngleMarkers(entryX, entryY, exitX, exitY, incidentAngle, refractionAngle, exitAngle) {
// 入射角文本
ctx.fillStyle = '#e74c3c';
ctx.font = 'bold 14px Arial';
ctx.fillText(`入射角: ${incidentAngle.toFixed(1)}°`, entryX - 100, entryY - 15);
// 折射角文本
ctx.fillStyle = '#27ae60';
ctx.fillText(`折射角: ${refractionAngle.toFixed(1)}°`, entryX - 100, entryY + 25);
// 出射角文本
ctx.fillStyle = '#2980b9';
ctx.fillText(`出射角: ${exitAngle.toFixed(1)}°`, exitX - 100, exitY + 25);
}
// 将波长转换为颜色
function wavelengthToColor(wavelength) {
let r, g, b;
if (wavelength >= 380 && wavelength < 440) {
r = (-(wavelength - 440) / (440 - 380));
g = 0.0;
b = 1.0;
} else if (wavelength >= 440 && wavelength < 490) {
r = 0.0;
g = ((wavelength - 440) / (490 - 440));
b = 1.0;
} else if (wavelength >= 490 && wavelength < 510) {
r = 0.0;
g = 1.0;
b = (-(wavelength - 510) / (510 - 490));
} else if (wavelength >= 510 && wavelength < 580) {
r = ((wavelength - 510) / (580 - 510));
g = 1.0;
b = 0.0;
} else if (wavelength >= 580 && wavelength < 645) {
r = 1.0;
g = (-(wavelength - 645) / (645 - 580));
b = 0.0;
} else if (wavelength >= 645 && wavelength <= 780) {
r = 1.0;
g = 0.0;
b = 0.0;
} else {
r = 0.0;
g = 0.0;
b = 0.0;
}
// 强度调整
let factor;
if (wavelength >= 380 && wavelength < 420) {
factor = 0.3 + 0.7 * (wavelength - 380) / (420 - 380);
} else if (wavelength >= 420 && wavelength < 700) {
factor = 1.0;
} else if (wavelength >= 700 && wavelength <= 780) {
factor = 0.3 + 0.7 * (780 - wavelength) / (780 - 700);
} else {
factor = 0.0;
}
r = Math.round(r * factor * 255);
g = Math.round(g * factor * 255);
b = Math.round(b * factor * 255);
return `rgb(${r}, ${g}, ${b})`;
}
// 动画循环
function animate() {
if (isAnimating) {
draw();
}
animationFrameId = requestAnimationFrame(animate);
}
// 事件监听器
incidentAngleSlider.addEventListener('input', function() {
incidentAngle = parseFloat(this.value);
incidentAngleValue.textContent = incidentAngle + '°';
draw();
});
wavelengthSlider.addEventListener('input', function() {
wavelength = parseFloat(this.value);
wavelengthValue.textContent = wavelength + 'nm';
draw();
});
glassRefractiveIndexSlider.addEventListener('input', function() {
glassRefractiveIndex = parseFloat(this.value);
glassRefractiveIndexValue.textContent = glassRefractiveIndex.toFixed(1);
draw();
});
glassWidthSlider.addEventListener('input', function() {
glassWidth = parseFloat(this.value);
const widthText = glassWidth < 200 ? '窄' :
glassWidth < 300 ? '中等' : '宽';
glassWidthValue.textContent = widthText;
draw();
});
glassThicknessSlider.addEventListener('input', function() {
glassThickness = parseFloat(this.value);
const thicknessText = glassThickness < 100 ? '薄' :
glassThickness < 150 ? '中等' : '厚';
glassThicknessValue.textContent = thicknessText;
draw();
});
toggleAnimationButton.addEventListener('click', function() {
isAnimating = !isAnimating;
});
resetButton.addEventListener('click', function() {
incidentAngleSlider.value = 30;
wavelengthSlider.value = 550;
glassRefractiveIndexSlider.value = 1.5;
glassWidthSlider.value = 250;
glassThicknessSlider.value = 120;
incidentAngle = 30;
wavelength = 550;
glassRefractiveIndex = 1.5;
glassWidth = 250;
glassThickness = 120;
incidentAngleValue.textContent = '30°';
wavelengthValue.textContent = '550nm';
glassRefractiveIndexValue.textContent = '1.5';
glassWidthValue.textContent = '中等';
glassThicknessValue.textContent = '中等';
draw();
});
// 初始化
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
animate();
</script>
</body>
</html>目录 返回
首页
