news 2026/4/23 14:04:16

【无标题】用 HTML/CSS/JS 实现光的折射控制器:直观演示斯涅尔定律

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【无标题】用 HTML/CSS/JS 实现光的折射控制器:直观演示斯涅尔定律
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>光的折射控制器</title> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: "Microsoft Yahei", sans-serif; } body { padding: 20px; background-color: #f5f5f5; } .container { max-width: 800px; margin: 0 auto; background: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } h1 { text-align: center; color: #333; margin-bottom: 30px; } .controls { display: flex; flex-wrap: wrap; gap: 20px; margin-bottom: 30px; padding: 20px; background: #f8f8f8; border-radius: 8px; } .control-group { flex: 1; min-width: 200px; } label { display: block; margin-bottom: 8px; font-weight: bold; color: #555; } input[type="range"] { width: 100%; height: 6px; accent-color: #007bff; } input[type="color"] { width: 100%; height: 40px; border: none; cursor: pointer; } .value-display { margin-top: 5px; color: #666; font-size: 14px; } .canvas-container { position: relative; width: 100%; height: 400px; border: 1px solid #ddd; background: #fafafa; overflow: hidden; } #refractionCanvas { width: 100%; height: 100%; } .medium-label { position: absolute; font-weight: bold; color: #333; padding: 5px 10px; background: rgba(255,255,255,0.8); border-radius: 4px; } .medium1-label { top: 20px; left: 20px; } .medium2-label { bottom: 20px; left: 20px; } </style> </head> <body> <div class="container"> <h1>光的折射控制器</h1> <div class="controls"> <div class="control-group"> <label for="incidentAngle">入射角 (°)</label> <input type="range" id="incidentAngle" min="0" max="89" value="30"> <div class="value-display" id="incidentAngleValue">30°</div> </div> <div class="control-group"> <label for="n1">介质1 折射率</label> <input type="range" id="n1" min="1.0" max="2.0" step="0.01" value="1.0"> <div class="value-display" id="n1Value">1.0</div> </div> <div class="control-group"> <label for="n2">介质2 折射率</label> <input type="range" id="n2" min="1.0" max="2.0" step="0.01" value="1.5"> <div class="value-display" id="n2Value">1.5</div> </div> <div class="control-group"> <label for="lightColor">光线颜色</label> <input type="color" id="lightColor" value="#ff0000"> <div class="value-display" id="lightColorValue">红色 (#ff0000)</div> </div> </div> <div class="canvas-container"> <canvas id="refractionCanvas"></canvas> <div class="medium-label medium1-label">介质1</div> <div class="medium-label medium2-label">介质2</div> </div> </div> <script> // 获取DOM元素 const canvas = document.getElementById('refractionCanvas'); const ctx = canvas.getContext('2d'); const incidentAngleInput = document.getElementById('incidentAngle'); const n1Input = document.getElementById('n1'); const n2Input = document.getElementById('n2'); const lightColorInput = document.getElementById('lightColor'); const incidentAngleValue = document.getElementById('incidentAngleValue'); const n1Value = document.getElementById('n1Value'); const n2Value = document.getElementById('n2Value'); const lightColorValue = document.getElementById('lightColorValue'); // 初始化画布尺寸 function resizeCanvas() { const container = canvas.parentElement; canvas.width = container.offsetWidth; canvas.height = container.offsetHeight; drawRefraction(); } // 计算折射角(斯涅尔定律:n1*sin(θ1) = n2*sin(θ2)) function calculateRefractionAngle(incidentAngle, n1, n2) { const incidentRad = incidentAngle * Math.PI / 180; const sinIncident = Math.sin(incidentRad); const sinRefraction = (n1 * sinIncident) / n2; // 处理全反射 if (sinRefraction > 1) { return -1; // 全反射 } return Math.asin(sinRefraction) * 180 / Math.PI; } // 绘制折射效果 function drawRefraction() { // 获取参数 const incidentAngle = parseFloat(incidentAngleInput.value); const n1 = parseFloat(n1Input.value); const n2 = parseFloat(n2Input.value); const lightColor = lightColorInput.value; const refractionAngle = calculateRefractionAngle(incidentAngle, n1, n2); // 清空画布 ctx.clearRect(0, 0, canvas.width, canvas.height); // 画布中心和介质分界线 const centerX = canvas.width / 2; const divideY = canvas.height / 2; // 绘制介质分界线 ctx.beginPath(); ctx.moveTo(0, divideY); ctx.lineTo(canvas.width, divideY); ctx.strokeStyle = '#333'; ctx.lineWidth = 2; ctx.stroke(); // 绘制法线(虚线) ctx.beginPath(); ctx.moveTo(centerX, 0); ctx.lineTo(centerX, canvas.height); ctx.strokeStyle = '#888'; ctx.setLineDash([5, 5]); ctx.lineWidth = 1; ctx.stroke(); ctx.setLineDash([]); // 计算入射光线坐标 const incidentRad = incidentAngle * Math.PI / 180; const rayLength = Math.max(canvas.width, canvas.height) / 2; // 入射光线起点(左上) const incidentStartX = centerX - rayLength * Math.sin(incidentRad); const incidentStartY = divideY - rayLength * Math.cos(incidentRad); // 入射光线终点(交点) const incidentEndX = centerX; const incidentEndY = divideY; // 绘制入射光线 ctx.beginPath(); ctx.moveTo(incidentStartX, incidentStartY); ctx.lineTo(incidentEndX, incidentEndY); ctx.strokeStyle = lightColor; ctx.lineWidth = 3; ctx.stroke(); // 绘制入射光线箭头 drawArrow(ctx, incidentEndX, incidentEndY, incidentStartX, incidentStartY, lightColor); // 绘制折射/反射光线 if (refractionAngle === -1) { // 全反射 const reflectEndX = centerX + rayLength * Math.sin(incidentRad); const reflectEndY = divideY - rayLength * Math.cos(incidentRad); ctx.beginPath(); ctx.moveTo(incidentEndX, incidentEndY); ctx.lineTo(reflectEndX, reflectEndY); ctx.strokeStyle = lightColor; ctx.lineWidth = 3; ctx.stroke(); // 绘制反射箭头 drawArrow(ctx, incidentEndX, incidentEndY, reflectEndX, reflectEndY, lightColor); // 标注全反射 ctx.font = '16px Microsoft Yahei'; ctx.fillStyle = '#ff4444'; ctx.fillText('全反射', centerX + 50, divideY - 50); } else { // 折射光线 const refractionRad = refractionAngle * Math.PI / 180; const refractionEndX = centerX + rayLength * Math.sin(refractionRad); const refractionEndY = divideY + rayLength * Math.cos(refractionRad); ctx.beginPath(); ctx.moveTo(incidentEndX, incidentEndY); ctx.lineTo(refractionEndX, refractionEndY); ctx.strokeStyle = lightColor; ctx.lineWidth = 3; ctx.stroke(); // 绘制折射箭头 drawArrow(ctx, incidentEndX, incidentEndY, refractionEndX, refractionEndY, lightColor); } // 标注角度 ctx.font = '14px Microsoft Yahei'; ctx.fillStyle = '#333'; // 入射角标注 ctx.fillText(`入射角: ${incidentAngle}°`, centerX + 20, divideY - 20); // 折射角标注 if (refractionAngle !== -1) { ctx.fillText(`折射角: ${refractionAngle.toFixed(1)}°`, centerX + 20, divideY + 40); } // 折射率标注 ctx.fillText(`n1: ${n1}`, 20, 30); ctx.fillText(`n2: ${n2}`, 20, canvas.height - 10); } // 绘制箭头 function drawArrow(ctx, fromX, fromY, toX, toY, color) { const headLength = 10; // 箭头长度 const angle = Math.atan2(toY - fromY, toX - fromX); ctx.beginPath(); ctx.moveTo(toX, toY); ctx.lineTo( toX - headLength * Math.cos(angle - Math.PI / 6), toY - headLength * Math.sin(angle - Math.PI / 6) ); ctx.lineTo( toX - headLength * Math.cos(angle + Math.PI / 6), toY - headLength * Math.sin(angle + Math.PI / 6) ); ctx.closePath(); ctx.fillStyle = color; ctx.fill(); } // 更新数值显示 function updateDisplayValues() { incidentAngleValue.textContent = `${incidentAngleInput.value}°`; n1Value.textContent = `${n1Input.value}`; n2Value.textContent = `${n2Input.value}`; lightColorValue.textContent = `${getColorName(lightColorInput.value)} (${lightColorInput.value})`; } // 颜色值转名称(简化版) function getColorName(hex) { const colorMap = { '#ff0000': '红色', '#00ff00': '绿色', '#0000ff': '蓝色', '#ffff00': '黄色', '#ff00ff': '紫色', '#00ffff': '青色', '#000000': '黑色', '#ffffff': '白色' }; return colorMap[hex] || '自定义颜色'; } // 绑定事件 incidentAngleInput.addEventListener('input', () => { updateDisplayValues(); drawRefraction(); }); n1Input.addEventListener('input', () => { updateDisplayValues(); drawRefraction(); }); n2Input.addEventListener('input', () => { updateDisplayValues(); drawRefraction(); }); lightColorInput.addEventListener('input', () => { updateDisplayValues(); drawRefraction(); }); // 窗口大小变化时重绘 window.addEventListener('resize', resizeCanvas); // 初始化 resizeCanvas(); updateDisplayValues(); </script> </body> </html>

功能说明:

  1. 交互控件

    • 入射角调节(0°~89°):控制入射光线与法线的夹角
    • 介质 1/2 折射率调节(1.0~2.0):模拟不同介质(如空气、水、玻璃)
    • 光线颜色选择:自定义光线显示颜色
  2. 可视化效果

    • 清晰区分两种介质,显示介质分界线和法线(虚线)
    • 实时绘制入射光线和折射 / 反射光线,带箭头指示方向
    • 标注入射角、折射角(全反射时标注 “全反射”)
    • 自动处理全反射场景(当折射角计算结果超出范围时)
  3. 适配性

    • 响应式布局,适配不同屏幕尺寸
    • 基于 Canvas 绘制,保证光线效果流畅
    • 引用国内 CDN 的 jQuery(仅备用,核心逻辑未依赖)

使用方法:

  1. 将代码保存为.html文件(如refraction.html
  2. 用浏览器打开该文件即可使用
  3. 拖动滑块调节参数,颜色选择器更改光线颜色,实时查看折射效果
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 11:16:30

大专市场营销专业,有哪些实用证书可以提升竞争力?

市场营销专业实用证书推荐市场营销专业的学生或从业者可以通过考取相关证书提升竞争力。以下是一些实用证书的详细介绍&#xff0c;包括CDA数据分析师证书。1. CDA数据分析师证书CDA&#xff08;Certified Data Analyst&#xff09;数据分析师证书是由经管之家&#xff08;原人…

作者头像 李华
网站建设 2026/4/23 12:31:58

科研常用工具

这个博客记录我平时经常访问的一些网站和工具&#xff08;不定期更新&#xff09;&#xff1a; UniProt Downloads | UniProt help | UniProt AAindex: Amino acid index database EMBOSS NEEDLE EMBOSS WATER The Human Protein Atlas DrugBank Release Version 5.1.13 …

作者头像 李华
网站建设 2026/4/23 13:03:46

Java毕设项目推荐-基于springboot图书借阅管理系统基于Java+springboot的图书管理系统【附源码+文档,调试定制服务】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/4/23 13:03:36

【踩坑笔记】ARM平台上Qt与Halcon的集成开发

嵌入式视觉开发中&#xff0c;Qt的界面优势和Halcon的强大图像处理能力无疑是黄金搭档。但当我尝试将它们结合并部署到ARM板上时&#xff0c;整整一个月的折腾几乎让我想要放弃。版本选择、编译配置的坑&#xff0c;特别是Halcon授权的限制&#xff0c;几乎让我陷入绝境。然而&…

作者头像 李华
网站建设 2026/4/23 10:42:14

【课程设计/毕业设计】基于Spring Boot的图书馆管理系统基于springboot的图书管理系统【附源码、数据库、万字文档】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华