双缝干涉:把两条缝的图样叠起来,光为什么自己跟自己打架
一块挡板上开两条平行细缝,单色光打过去,屏幕上映出的不是两条亮线,而是一排等间距的明暗条纹。把其中一条缝遮住,条纹立刻消失、只剩一团中间亮两边暗的光斑——同一束光,只因「知道不知道它走了哪条缝」,结果就完全不同。
静态图讲不清两件事:条纹间距到底由什么决定?为什么挡住一条缝,条纹就没了? 所以我把它做成了一个能拖、能遮、能一颗一颗发射光子的网页:上半屏是俯视波浪场(两条缝当成两个同频点波源,实时叠加并着色),下半屏是屏上光强曲线 I(y)/I₀ 与光子累积点阵,右边一块数值面板把 ΔY 和 y₁/y₂/y₃ 算给你看。
几条关键信息先摆出来:
- 源码:Demo Park 公开仓库 → https://atomgit.com/deli007/demo_park/tree/main/codearts-double-slit-lab,
index.html可直接下载- 生成物:单个
index.html(约 28 KB),纯前端、零依赖、不联网、不上传,全部计算在本地完成- 本地运行:双击文件即可;或
python -m http.server 8000- 本地验证:1920×1200 视口;默认 λ=550nm、d=0.30mm、L=1.50m 下 ΔY=2.750mm、y₁/y₂/y₃=2.750/5.500/8.250mm;自检 8/8 通过;连发 3600 颗光子后主峰落在 2.649mm,与理论 2.750mm 差 3.7%
它算的是理想化双缝。缝是无限细的抽象线(只有一个「缝间距 d」,没有缝宽对条纹的调制),光是标量波、没有偏振,也没有单光子探测器的计数噪声模型。下面会如实写它的边界。

为什么值得看:两条缝的差别,全在一个相位差里
屏上某点 y 到底亮还是暗,只看两条缝过来的光差了多少路程。这个路程差 Δ 写成相位就是 φ = π·d·y / (λ·L)(d 是缝间距、L 是缝到屏的距离),两束光叠加后的相对光强是:
I(y)/I₀ = cos²(π·d·y / (λ·L)) 光强
ΔY = λ·L / d 条纹间距
yₘ = m·λ·L / d 第 m 级亮纹位置
- y=0(屏中心)处两条缝的路程完全相等,φ=0,cos²=1——所以中心永远是亮纹,不管参数怎么改;
- 相邻亮纹之间相位正好涨 π,于是 y 走
λL/d就又亮一次,这就是 ΔY; - ΔY 和 d 成反比:缝拉得越开、条纹越密;ΔY 和 λ、L 都成正比:波长越长、屏越远,条纹越宽。
页面里有三个实现决定值得说清楚:
- 波浪场和光强曲线共用同一套公式。
phaseRad()把物理量统一换算成相位,Irel()再取cos²;波浪场那两个点源用的也是同一个随距离衰减的相位差。所以上图里加强的方向和下图曲线的高峰位置是对齐的,不是两张各画各的图。 - 光子落点是按 I(y) 的累积分布采样出来的。
makeSampler()先把曲线在 [-Ymax, Ymax] 上离散成 3000 段的累积分布,再反解出落点。所以「一颗一颗发射」不会均匀乱撒,而是老老实实按亮纹概率多、暗纹概率少来落——累积久了就还原出条纹。 - 波长到颜色是一条真正的光谱映射。
wavelengthToRGB()用分段线性插值算出 RGB(紫→蓝→绿→黄→红),再乘一个端部衰减因子压暗两端,而不是简单按色相环取色。
也说几处真实的边界:
- 缝是理想细缝。真实双缝实验里缝宽决定了单缝衍射包络——条纹会被一个中间亮、两边递减的包络调制,所以远处的条纹会越来越暗。这里干涉项是纯
cos²,两侧条纹一样高(单缝模式下的包络是另一套sinc²公式)。 - 光是标量波。没有偏振、没有光子的路径纠缠,屏幕亮度是概率意义上的,不是单个光子的轨迹。
- 光点累积没有固定种子,用的是
Math.random();每次连发点阵都不一样,但直方图轮廓会稳定收敛到同一条 I(y) 曲线上。
任务描述
需求是直接粘进码道 Web 输入框的,原文如下:
开发一个单文件 index.html 的「双缝干涉实验室」网页应用,纯前端、零依赖、不联网、不上传任何数据。
画面分两块:
1) 上方是俯视波浪场:两条缝当作两个同频点波源,各自向右侧发出圆形波,叠加处按瞬时振幅着色(波峰偏暖色、波谷偏冷色),能直接看出加强区和相消区连成的射线状条纹。
2) 下方是观察屏上的光强分布图:横轴是屏上位置 y(单位毫米),纵轴是相对光强 I/I0,曲线随参数实时重画。
物理公式必须真算,不要假数据:
- 双缝干涉相对光强 I(y)/I0 = cos^2(pi * d * y / (lambda * L))
- 条纹间距 deltaY = lambda * L / d
- 第 m 级亮纹位置 y_m = m * lambda * L / d
- 波长到颜色用可见光谱近似映射(380nm 偏紫,450nm 蓝,520nm 绿,590nm 黄,620nm 橙,700nm 红)
交互:
- 波长滑杆 380~750 nm,默认 550
- 缝间距滑杆 0.10~1.00 mm,默认 0.30
- 屏距离滑杆 0.50~3.00 m,默认 1.50
- 每个滑杆旁边配一个数字输入框可以精确输入;输入为空、非数字或超出范围时,页面上要给出明确中文提示(说明合法范围),并且保留上一次的有效值继续显示,页面不能崩
- 「光点累积」模式:可以点「发射 1 颗光子」单发,也可以点「连发 300 颗」;每颗光子按 I(y) 归一化后的概率密度随机落在屏上,累积成点阵条纹;提供「清空」按钮把累积点清零
- 「挡住一条缝」开关:关掉其中一条缝后,波浪场只剩一个波源,光强曲线从等间距干涉条纹变成单缝衍射包络(中央最亮、两侧迅速衰减),用来对比「知道走了哪条缝,条纹就没了」;再打开恢复干涉条纹
- 右上角数值面板实时显示:当前波长对应颜色、条纹间距 deltaY(毫米)、中央到第 1/2/3 级亮纹的位置 y1/y2/y3(毫米)、以及当前参数下一条缝与另一条缝到屏中心的光程差
自检:放一个「运行自检」按钮,点下去跑 8 条数值断言并在页面上逐条显示通过/失败,最后显示「自检 8/8 通过」或列出失败条目。断言至少包含:I(0) 等于 I0;y = deltaY/2 处强度约等于 0;条纹间距与缝间距成反比;条纹间距与波长成正比;条纹间距与屏距离成正比;650nm 的条纹间距大于 450nm 的;屏中心永远是亮纹;光子累积直方图的峰位与理论 y_m 的相对误差小于 10%。
界面:深色、简洁、字号够大,画布随窗口自适应。完成后打开预览,让我直接看到运行效果。
码道 Web 交回的是单文件 index.html,自己起了内置预览。下面的数字全部来自我在本机浏览器里对这个文件的重跑,不是转述它的总结。
它实际长成了什么样:三个滑杆各拖一遍,ΔY 跟着怎么动
面板上默认参数是 λ=550nm、d=0.30mm、L=1.50m,它算出的 ΔY=2.750mm、y₁/y₂/y₃=2.750/5.500/8.250mm,屏中心光程差 0.0000mm。为了看清 ΔY 到底由谁决定,我把三个滑杆分别扫了一遍,每次都只动一个变量:
| 固定 | 变动 | ΔY 实测 | ΔY × 变量 |
|---|---|---|---|
| d=0.30、L=1.50 | λ=450nm | 2.250 mm | ΔY/λ = 0.00500 |
| d=0.30、L=1.50 | λ=550nm | 2.750 mm | ΔY/λ = 0.00500 |
| d=0.30、L=1.50 | λ=650nm | 3.250 mm | ΔY/λ = 0.00500 |
| λ=550、L=1.50 | d=0.10mm | 8.250 mm | ΔY·d = 0.825 |
| λ=550、L=1.50 | d=0.30mm | 2.750 mm | ΔY·d = 0.825 |
| λ=550、L=1.50 | d=0.45mm | 1.833 mm | ΔY·d = 0.825 |
| λ=550、d=0.30 | L=1.00m | 1.833 mm | ΔY/L = 1.833 |
| λ=550、d=0.30 | L=1.50m | 2.750 mm | ΔY/L = 1.833 |
| λ=550、d=0.30 | L=3.00m | 5.500 mm | ΔY/L = 1.833 |
三列比值全是常数,说明页面里 ΔY 确实严格正比于 λ 和 L、反比于 d,没有掺进别的因子。波长那一档最直观:450nm 的蓝光条纹最密,650nm 的红光最疏,同一个屏幕上条纹宽度差出 44%(2.250 → 3.250)。
光子累积那部分是这篇的主角。 连点 12 次「连发 300 颗」,累积到 3600 颗,屏幕上的点阵是这样的:

- 点阵主峰落在 2.649mm,理论 y₁=2.750mm,相对偏差 3.7%;
- 3600 颗里有 1542 颗(42.8%) 落在 |y| < 1.5ΔY 的范围内,也就是被压进了中央那几条亮纹里;
- 页面自检用 16000 颗光子重跑同一项,峰位 2.605mm、相对误差 5.29%——样本更多时峰位抖动量级就在几个百分点,这是蒙特卡洛的噪声底线,不是公式不准。
把一条缝挡住,条纹就没了。 打开「挡住一条缝(单缝模式)」开关后:波浪场只剩一个点源向外发散(另一条缝被标了红叉),屏上曲线从等间距的 cos² 换成单缝的 sinc² 包络——中央最高、往两侧迅速衰减,而且面板里 ΔY、y₁、y₂、y₃ 四格直接变成「—(单缝)」。

之所以会有这个对比:干涉条纹的前提是「不知道光子走的哪条缝」。两条缝都开着时,屏上每一点都是两条路径的叠加,相位差决定加减;一旦遮掉一条,只剩一条路径可走,就不存在「两条路径差多少」这件事,条纹自然消失。这也是量子力学里那句常被引用的话的直观版本。
踩坑与边界:四处真实的不一致
一、590nm 的颜色名字不是需求里说的「黄」。 我在需求里点名了「590nm 黄」,但页面把色名分成了 7 档:紫(<430)、蓝(430~479)、蓝绿(480~514)、绿(515~559)、黄绿(560~589)、橙(590~629)、红(≥630)。整套档位里没有单独的「黄」,590nm 正好落在「橙」档。实测 590nm 与 620nm 都是「橙」,560/570/580nm 都是「黄绿」。它的 RGB 插值其实是按物理分段来的(590nm 给的是偏黄橙的 r=1、g≈0.85),所以问题只出在色名的分档阈值上,不是颜色算错了。这提醒我:需求里点名的锚点,最好连「分几档、档位边界在哪」一起写清楚。
二、「必须是数字」这条提示,实际操作中走不到。 三个输入框都是 <input type="number">。浏览器不允许把 abc 这类文本写进 number 输入框的 value——脚本里赋值后读回来已经是空字符串,所以页面永远命中「不能为空」那条分支,而不是「必须是数字」。我实测:给 d 填 abc,提示是「缝间距 d不能为空,请输入 0.1 ~ 1 之间的数字(单位 mm)。已恢复上次有效值。」,和真的留空完全一样。三条校验分支写得很全,但其中一条被输入框类型挡在了更前面。
三、错误提示 5 秒后自动消失。 showErr() 里挂了一个 5 秒的 setTimeout 把提示清空,而输入框和滑杆已经立刻回退到旧值。所以填错之后如果没马上看,画面上只会看到「数值没变」,看不到任何解释——想核对提示原文得赶在 5 秒内,或者再填一次。这个是设计选择(提示不长期占位),但它让「页面给出明确提示」这件事变得有时效性。
四、切到单缝模式后,数值面板有几格变空。 需求写的是「数值面板实时显示 ΔY 和 y₁/y₂/y₃」,而单缝模式下这四格显示的是「—(单缝)」。从物理上讲这是对的(单缝没有干涉条纹间距),但严格对照需求就是「有 4 格不再是数值」。
顺带一个没踩到的点: 我本来担心「连发 300 颗」点太快会让累积超过上限,实测光子有硬上限 5000 颗,到了会提示「已达上限 5000 颗,请清空后再发射。」,不会越界也不会卡死。
自检 8/8,但自检不能当验收
点「运行自检」,8 条断言我本机重跑的原文(面板逐条列出)是:
自检 8/8 通过 ✅
✓ 通过:I(0) 恒等于 I₀ 屏中心相对光强 = cos²(0) = 1
✓ 通过:y = ΔY/2 处强度 ≈ 0 半条纹间距处相位差为 π,cos²(π/2)=0;I(1.375mm)=2.59e-32
✓ 通过:条纹间距与缝间距成反比 ΔY·d 为常数:0.825000 = 0.825000
✓ 通过:条纹间距与波长成正比 ΔY/λ 为常数:0.005000000 ≈ 0.005000000
✓ 通过:条纹间距与屏距离成正比 ΔY/L 为常数:1.833333 = 1.833333
✓ 通过:650nm 的条纹间距 > 450nm 的 ΔY(650nm)=3.2500mm > ΔY(450nm)=2.2500mm
✓ 通过:屏中心永远是亮纹 对多组参数 I(0)=1(含单缝模式)
✓ 通过:光子累积峰位与理论 y₁ 误差 < 10% 实测峰位 2.605mm,理论 ΔY=2.750mm,相对误差 5.29%
八条覆盖了「中心恒亮」「半间距处相消」「ΔY 与三个变量的比例关系」「颜色对应的单调性」「光子分布收敛」。但它验的是公式和采样的自洽性,不验画面:曲线画没画对、纹理对不对、单缝包络长得对不对,这些它一句都没查——图上那两种情况(图 2 的点阵聚束、图 3 的包络切换)是我自己一张张看出来的。

失败路径写得不错,但提示是短暂出现的,三种非法输入的实际提示原文如下:
| 输入 | 提示原文 |
|---|---|
缝间距填 2 |
缝间距 d超出范围:2mm。合法范围:0.1 ~ 1 mm,已恢复上次有效值。 |
屏距离填 0.2 |
屏距离 L超出范围:0.2m。合法范围:0.5 ~ 3 m,已恢复上次有效值。 |
| 波长留空 | 波长 λ不能为空,请输入 380 ~ 750 之间的数字(单位 nm)。已恢复上次有效值。 |
三种都回退了上一次的有效值(550/0.30/1.50),页面继续正常显示,没有白屏。

准备环境:进入码道 Web
浏览器打开码道 Web 版:https://devcloud.cn-north-4.huaweicloud.com/chat?source=dmzntgwsf&sourcead=dmzntgwwbwz,登录后就能在对话窗口输入需求,不需要装软件。
码道有三种使用方式:WebUI(浏览器对话)、TUI(终端命令行)和桌面 IDE(IDE 插件)。本文用 WebUI 版演示。
本地复现
生成物只有一个 index.html,复现没有依赖:
- 从 Demo Park 本案例目录下载
index.html。 - 双击打开。默认 λ=550nm、d=0.30mm、L=1.50m,上半屏立刻能看到两个波源叠出的射线状条纹。
- 想走静态服务也行:
python -m http.server 8000,然后访问http://localhost:8000。 - 把窗口拉到一个较大的视口(我用的是 1920×1200),两块画布都会跟着窗口自适应重画。
下面这些数是我本地逐条跑出来的,都能自己复验:
- 画布:视口 1920×1200 时波浪场画布 1302×552、光强画布 1302×444,都随窗口宽度自适应。
- 默认参数:ΔY=2.750mm,y₁=2.750mm、y₂=5.500mm、y₃=8.250mm,屏中心光程差 0.0000mm。
- 三组扫描:ΔY/λ=0.00500(450/550/650nm)、ΔY·d=0.825(0.10/0.30/0.45mm)、ΔY/L=1.833(1.00/1.50/3.00m),各自都是常数。
- 光子累积:3600 颗时主峰 2.649mm(理论 2.750mm,偏差 3.7%),其中 42.8% 落在 |y|<1.5ΔY 内。
- 单缝模式:波浪场只剩一个波源,曲线换成 sinc² 包络,ΔY 与 y₁/y₂/y₃ 显示「—(单缝)」。
- 自检:点「运行自检」8/8 通过,每条都带实测数字。
- 非法输入:越界 / 空值都给出中文提示并回退旧值,不白屏。
使用码道体会
- 把公式三件套都写进需求。 我在需求里同时给了
I(y)/I₀、ΔY、yₘ三个公式和各自的单位,它交回来的页面就有了「曲线 + 面板数字 + 自检断言」三层互相印证的东西。只写「做一个双缝干涉动画」,很可能只会得到好看的波纹,量不出 ΔY。 - 要求它把关键中间量摆出来。 「数值面板实时显示 ΔY 和 y₁/y₂/y₃」这一条回报最大:上面那张扫描表我一次都没用计算器——改一个滑杆、读一次面板,三次就够看出比例关系。
- 物理量之间的比例关系,交给它做成断言。 需求里的 8 条自检断言(反比、正比、单调、中心恒亮)不是随便写的,它把「公式对不对」变成了「点一下就能看的勾」,我才敢在文章里直接引用后续测量的数字。
- 没写进需求的分档细节,它就自己定一套。 颜色那条我只给了 6 个锚点(380 紫 / 450 蓝 / 520 绿 / 590 黄 / 620 橙 / 700 红),没有给分档边界,于是它把「黄绿」当一档、让 590nm 落进了「橙」。锚点写清楚,档位边界也得写清楚。
- 验收要自己动手。 自检 8/8 全绿,但它验的是公式自洽,没验画面;点阵有没有真的聚成条纹、单缝包络有没有真的切过去,都是我自己看图确认的。Agent 说完成不算验收。
亲手点三下
页面打开就能验,三下够了:
- 看条纹间距:把「波长 λ」滑杆从 380 慢慢推到 750,盯住面板里的 ΔY 和屏上的曲线。曲线会跟着疏密变化,ΔY 从 1.900mm 一路涨到 3.750mm;换成推「缝间距 d」,方向正好相反——d 越大条纹越密。
- 看条纹消失:把「挡住一条缝(单缝模式)」开关打开,上半屏的波浪场会从两个源变成一个源,下半屏的曲线从等间距条纹变成中间高两边低的包络,面板里 ΔY 那几格变成「—(单缝)」;再关掉开关,条纹原样回来。
- 看失败路径:在「缝间距 d」的数字框里填
2回车,必须看到一行中文红字提示、输入框退回 0.30、页面不白屏;再把「波长 λ」清空回车,提示会换成「不能为空」那一条。
直接下载试玩
- Demo Park 仓库:https://atomgit.com/deli007/demo_park
- 本案例目录:https://atomgit.com/deli007/demo_park/tree/main/codearts-double-slit-lab
总结
把双缝干涉做成能拖的页面之后,最值得记住的不是那排条纹,而是「挡住一条缝」那一下:条纹之所以存在,靠的是两条路径的相位差——把其中一条断掉,屏上每一点就只剩一个来源,装不出来干涉。所以条纹不是光照出来的花纹,而是「同一个光子有两条路可走」的直接后果;ΔY = λL/d 只是这件事在几何上的刻度。想接着玩,可以试试把缝间距 d 拉到最小(0.10mm)看条纹宽到只剩一两条、把 L 推到 3m 看条纹被拉得多开,或者在单缝模式下把波长换成 380nm,看单缝包络的中心亮斑是怎么收窄的。
- 点赞
- 收藏
- 关注作者
评论(0)