<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>光学折射原理交互演示</title>
<style>
body {
font-family: 'Microsoft YaHei', Arial, sans-serif;
line-height: 1.6;
color: #333;
max-width: 1000px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
h1, h2 {
color: #2c3e50;
text-align: center;
}
.container {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
padding: 20px;
margin-bottom: 20px;
}
.simulation-area {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
margin-bottom: 30px;
}
.canvas-container {
width: 60%;
min-width: 400px;
}
.controls {
width: 35%;
min-width: 300px;
padding: 15px;
background-color: #f9f9f9;
border-radius: 5px;
}
canvas {
border: 1px solid #ddd;
background-color: #e6f7ff;
display: block;
margin: 0 auto;
}
.control-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input[type="range"] {
width: 100%;
}
.value-display {
font-size: 14px;
color: #666;
margin-top: 5px;
}
.formula {
background-color: #f0f8ff;
padding: 15px;
border-radius: 5px;
margin: 20px 0;
text-align: center;
font-size: 18px;
overflow-x: auto;
}
.interactive-tool {
margin: 20px 0;
padding: 15px;
background-color: #f0f0f0;
border-radius: 5px;
}
button {
background-color: #3498db;
color: white;
border: none;
padding: 8px 15px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s;
}
button:hover {
background-color: #2980b9;
}
.quiz {
margin: 20px 0;
padding: 15px;
background-color: #e8f4f8;
border-radius: 5px;
}
.quiz-question {
font-weight: bold;
margin-bottom: 10px;
}
.quiz-option {
margin: 5px 0;
padding: 8px;
background-color: white;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s;
}
.quiz-option:hover {
background-color: #e1f5fe;
}
.feedback {
margin-top: 10px;
padding: 10px;
border-radius: 4px;
display: none;
}
.correct {
background-color: #d4edda;
color: #155724;
}
.incorrect {
background-color: #f8d7da;
color: #721c24;
}
.explanation {
margin-top: 10px;
font-size: 14px;
color: #555;
}
@media (max-width: 768px) {
.canvas-container, .controls {
width: 100%;
}
}
</style>
</head>
<body>
<div class="container">
<h1>光学折射原理交互演示</h1>
<div class="simulation-area">
<div class="canvas-container">
<canvas id="refractionCanvas" width="500" height="400"></canvas>
</div>
<div class="controls">
<h2>参数调整</h2>
<div class="control-group">
<label for="incidentAngle">入射角 (θ₁): <span id="incidentAngleValue">45</span>°</label>
<input type="range" id="incidentAngle" min="0" max="89" value="45" step="1">
</div>
<div class="control-group">
<label for="n1">介质1折射率 (n₁): <span id="n1Value">1.0</span></label>
<input type="range" id="n1" min="1" max="3" value="1.0" step="0.1">
</div>
<div class="control-group">
<label for="n2">介质2折射率 (n₂): <span id="n2Value">1.33</span></label>
<input type="range" id="n2" min="1" max="3" value="1.33" step="0.01">
</div>
<div class="control-group">
<label>折射角 (θ₂): <span id="refractionAngleValue">32.1</span>°</label>
</div>
<div class="control-group">
<button id="resetBtn">重置参数</button>
<button id="swapMediaBtn">交换介质</button>
</div>
</div>
</div>
<div class="formula">
斯涅尔定律 (折射定律):
<strong>n₁·sin(θ₁) = n₂·sin(θ₂)</strong>
</div>
<div class="interactive-tool">
<h2>折射角计算器</h2>
<div class="control-group">
<label for="calcN1">介质1折射率 (n₁):</label>
<input type="number" id="calcN1" value="1.0" step="0.01" min="1" max="3">
</div>
<div class="control-group">
<label for="calcN2">介质2折射率 (n₂):</label>
<input type="number" id="calcN2" value="1.33" step="0.01" min="1" max="3">
</div>
<div class="control-group">
<label for="calcAngle">入射角 (θ₁):</label>
<input type="number" id="calcAngle" value="45" step="1" min="0" max="89">°
</div>
<button id="calculateBtn">计算折射角</button>
<div id="calculationResult" class="value-display"></div>
</div>
</div>
<div class="container">
<h2>折射原理说明</h2>
<p>当光从一种介质斜射入另一种介质时,传播方向会发生偏折,这种现象称为光的折射。</p>
<p>折射现象遵循斯涅尔定律:n₁·sin(θ₁) = n₂·sin(θ₂),其中:</p>
<ul>
<li>n₁和n₂分别是两种介质的折射率</li>
<li>θ₁是入射角(入射光线与法线的夹角)</li>
<li>θ₂是折射角(折射光线与法线的夹角)</li>
</ul>
<p>折射率是描述介质对光的折射能力的物理量。真空的折射率为1,空气的折射率约为1.0003(通常近似为1),水的折射率约为1.33,玻璃的折射率约为1.5-1.7。</p>
<p>当光从折射率较小的介质(如空气)进入折射率较大的介质(如水)时,折射角小于入射角;反之,折射角大于入射角。</p>
</div>
<div class="container">
<h2>互动测验</h2>
<div class="quiz">
<div class="quiz-question">1. 当光线从空气(n=1.0)射入水(n=1.33)中时,如果入射角为30°,折射角会:</div>
<div class="quiz-option" data-correct="true">小于30°</div>
<div class="quiz-option">等于30°</div>
<div class="quiz-option">大于30°</div>
<div class="feedback"></div>
<div class="explanation" style="display:none;">
正确答案是"小于30°"。根据斯涅尔定律,当光从低折射率介质进入高折射率介质时,折射角小于入射角。
</div>
</div>
<div class="quiz">
<div class="quiz-question">2. 当入射角增大时,折射角会:</div>
<div class="quiz-option" data-correct="true">也随之增大</div>
<div class="quiz-option">减小</div>
<div class="quiz-option">保持不变</div>
<div class="feedback"></div>
<div class="explanation" style="display:none;">
正确答案是"也随之增大"。根据斯涅尔定律n₁·sin(θ₁) = n₂·sin(θ₂),当θ₁增大时,sin(θ₁)增大,因此sin(θ₂)也必须增大,意味着θ₂也增大。
</div>
</div>
<div class="quiz">
<div class="quiz-question">3. 当光线从水中射向空气时,如果入射角足够大,会发生什么现象?</div>
<div class="quiz-option">折射角等于90°</div>
<div class="quiz-option" data-correct="true">全反射</div>
<div class="quiz-option">光线会沿界面传播</div>
<div class="feedback"></div>
<div class="explanation" style="display:none;">
正确答案是"全反射"。当光从高折射率介质射向低折射率介质且入射角大于临界角时,会发生全反射现象,所有光都被反射回原介质。
</div>
</div>
</div>
<script>
// 获取DOM元素
const canvas = document.getElementById('refractionCanvas');
const ctx = canvas.getContext('2d');
// 控制元素
const incidentAngleSlider = document.getElementById('incidentAngle');
const n1Slider = document.getElementById('n1');
const n2Slider = document.getElementById('n2');
const incidentAngleValue = document.getElementById('incidentAngleValue');
const n1Value = document.getElementById('n1Value');
const n2Value = document.getElementById('n2Value');
const refractionAngleValue = document.getElementById('refractionAngleValue');
const resetBtn = document.getElementById('resetBtn');
const swapMediaBtn = document.getElementById('swapMediaBtn');
const calculateBtn = document.getElementById('calculateBtn');
const calculationResult = document.getElementById('calculationResult');
// 计算器输入
const calcN1 = document.getElementById('calcN1');
const calcN2 = document.getElementById('calcN2');
const calcAngle = document.getElementById('calcAngle');
// 初始化参数
let incidentAngle = 45; // 入射角,单位度
let n1 = 1.0; // 介质1折射率
let n2 = 1.33; // 介质2折射率
// 绘制折射演示
function drawRefraction() {
// 清除画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 设置坐标系原点在画布中心
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
// 绘制介质分界线
ctx.beginPath();
ctx.moveTo(0, centerY);
ctx.lineTo(canvas.width, centerY);
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.stroke();
// 标注介质
ctx.font = '16px Arial';
ctx.fillStyle = '#333';
ctx.fillText(`介质1 (n=${n1})`, 20, centerY - 20);
ctx.fillText(`介质2 (n=${n2})`, 20, centerY + 40);
// 计算折射角(弧度)
const incidentAngleRad = incidentAngle * Math.PI / 180;
let refractionAngleRad = Math.asin((n1 * Math.sin(incidentAngleRad)) / n2);
// 检查是否全反射
const criticalAngle = Math.asin(n2 / n1) * 180 / Math.PI;
let isTotalReflection = false;
if (n1 > n2 && incidentAngle >= criticalAngle) {
isTotalReflection = true;
refractionAngleRad = Math.PI / 2; // 设置为90度用于显示
}
// 转换为角度显示
const refractionAngle = refractionAngleRad * 180 / Math.PI;
refractionAngleValue.textContent = isTotalReflection ? "全反射" : refractionAngle.toFixed(1);
// 绘制法线
ctx.beginPath();
ctx.moveTo(centerX, 0);
ctx.lineTo(centerX, canvas.height);
ctx.strokeStyle = '#888';
ctx.setLineDash([5, 3]);
ctx.stroke();
ctx.setLineDash([]);
// 绘制入射光线
const rayLength = 200;
const startX = centerX - rayLength * Math.sin(incidentAngleRad);
const startY = centerY - rayLength * Math.cos(incidentAngleRad);
ctx.beginPath();
ctx.moveTo(startX, startY);
ctx.lineTo(centerX, centerY);
ctx.strokeStyle = '#e74c3c';
ctx.lineWidth = 2;
ctx.stroke();
// 绘制折射光线(如果不是全反射)
if (!isTotalReflection) {
const endX = centerX + rayLength * Math.sin(refractionAngleRad);
const endY = centerY + rayLength * Math.cos(refractionAngleRad);
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.lineTo(endX, endY);
ctx.strokeStyle = '#3498db';
ctx.lineWidth = 2;
ctx.stroke();
} else {
// 如果是全反射,绘制反射光线
const endX = centerX - rayLength * Math.sin(incidentAngleRad);
const endY = centerY - rayLength * Math.cos(incidentAngleRad);
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.lineTo(endX, endY);
ctx.strokeStyle = '#2ecc71';
ctx.lineWidth = 2;
ctx.setLineDash([5, 3]);
ctx.stroke();
ctx.setLineDash([]);
// 显示全反射提示
ctx.fillStyle = '#e67e22';
ctx.fillText('全反射', centerX + 10, centerY - 20);
}
// 绘制角度标注
drawAngleMarker(centerX, centerY, incidentAngleRad, true, '#e74c3c');
if (!isTotalReflection) {
drawAngleMarker(centerX, centerY, refractionAngleRad, false, '#3498db');
}
// 显示临界角信息(如果接近临界角)
if (n1 > n2 && incidentAngle >= criticalAngle - 5 && incidentAngle <= criticalAngle + 5) {
ctx.fillStyle = '#e67e22';
ctx.fillText(`临界角: ${criticalAngle.toFixed(1)}°`, centerX - 100, 30);
}
}
// 绘制角度标记
function drawAngleMarker(x, y, angle, isIncident, color) {
const radius = 30;
const startAngle = isIncident ? -Math.PI/2 : Math.PI/2;
const endAngle = isIncident ? startAngle - angle : startAngle + angle;
ctx.beginPath();
ctx.arc(x, y, radius, startAngle, endAngle, !isIncident);
ctx.strokeStyle = color;
ctx.lineWidth = 1;
ctx.stroke();
// 角度文本
const angleDeg = (angle * 180 / Math.PI).toFixed(0);
const textAngle = isIncident ? -angle/2 : angle/2;
const textRadius = radius + 10;
const textX = x + (isIncident ? -1 : 1) * textRadius * Math.sin(textAngle);
const textY = y - textRadius * Math.cos(textAngle);
ctx.fillStyle = color;
ctx.font = '14px Arial';
ctx.fillText(`${angleDeg}°`, textX - 10, textY + 5);
}
// 更新参数并重绘
function updateParameters() {
incidentAngle = parseFloat(incidentAngleSlider.value);
n1 = parseFloat(n1Slider.value);
n2 = parseFloat(n2Slider.value);
incidentAngleValue.textContent = incidentAngle;
n1Value.textContent = n1.toFixed(n1 % 1 === 0 ? 0 : 2);
n2Value.textContent = n2.toFixed(n2 % 1 === 0 ? 0 : 2);
drawRefraction();
}
// 重置参数
function resetParameters() {
incidentAngleSlider.value = 45;
n1Slider.value = 1.0;
n2Slider.value = 1.33;
updateParameters();
}
// 交换介质
function swapMedia() {
const temp = n1Slider.value;
n1Slider.value = n2Slider.value;
n2Slider.value = temp;
updateParameters();
}
// 计算折射角
function calculateRefractionAngle() {
const calcN1Val = parseFloat(calcN1.value);
const calcN2Val = parseFloat(calcN2.value);
const angle = parseFloat(calcAngle.value);
if (isNaN(calcN1Val) || isNaN(calcN2Val) || isNaN(angle)) {
calculationResult.textContent = "请输入有效数值!";
calculationResult.style.color = "red";
return;
}
if (angle < 0 || angle >= 90) {
calculationResult.textContent = "入射角必须在0-89度之间!";
calculationResult.style.color = "red";
return;
}
const angleRad = angle * Math.PI / 180;
const sinTheta2 = (calcN1Val * Math.sin(angleRad)) / calcN2Val;
if (sinTheta2 > 1) {
calculationResult.innerHTML = "计算结果: <strong>全反射</strong> (入射角大于临界角)";
calculationResult.style.color = "#e67e22";
} else {
const theta2 = Math.asin(sinTheta2) * 180 / Math.PI;
calculationResult.textContent = `折射角: ${theta2.toFixed(2)}°`;
calculationResult.style.color = "#2c3e50";
}
}
// 初始化测验交互
function initQuiz() {
const quizOptions = document.querySelectorAll('.quiz-option');
quizOptions.forEach(option => {
option.addEventListener('click', function() {
const isCorrect = this.getAttribute('data-correct') === 'true';
const feedback = this.parentElement.querySelector('.feedback');
const explanation = this.parentElement.querySelector('.explanation');
feedback.textContent = isCorrect ? "✓ 正确!" : "✗ 错误";
feedback.className = 'feedback ' + (isCorrect ? 'correct' : 'incorrect');
feedback.style.display = 'block';
explanation.style.display = 'block';
// 禁用所有选项
quizOptions.forEach(opt => {
opt.style.pointerEvents = 'none';
if (opt.getAttribute('data-correct') === 'true') {
opt.style.backgroundColor = '#d4edda';
}
});
});
});
}
// 事件监听
incidentAngleSlider.addEventListener('input', updateParameters);
n1Slider.addEventListener('input', updateParameters);
n2Slider.addEventListener('input', updateParameters);
resetBtn.addEventListener('click', resetParameters);
swapMediaBtn.addEventListener('click', swapMedia);
calculateBtn.addEventListener('click', calculateRefractionAngle);
// 初始化
window.addEventListener('load', function() {
updateParameters();
initQuiz();
});
</script>
</body>
</html>
index.html
style.css
index.js
index.html