物理直觉这东西,背再多公式也背不出来,得动手调。课本给的是静态图,视频是别人调好的参数,你只能看不能动。我想给初中生做一个能拖滑块、立刻看结果的玩意儿:九个模拟器,一个文件,双击就开,断网也行,手机上也自适应。

九个物理模拟器成果总览

为什么非得是"能动手调"的

我见过太多孩子把"45° 抛得最远"背得滚瓜烂熟,但你问他"30° 和 60° 谁远",他愣住。因为课本只画了一张 45° 的图,他没亲手把角度从 0 拖到 90,没看到射程先涨到 45° 再回落。这个"先涨后落"的形状,才是直觉。

视频解决不了这件事。视频里的参数是作者调好的,你只能看别人调。我要的是:孩子自己把弹簧拉硬一点,看到小球弹得更快;把摆长拉长一倍,看到周期变慢。而且这个变化得是即时的,拖一下滑块,画面立刻变。

这就把技术路线定死了:必须能在任何电脑、手机上直接打开,不装环境、不联网、不依赖服务器。能拷进 U 盘、能微信发文件、能传手机浏览器。排除了一圈方案,最后落在最朴素的东西上,单个 HTML 文件。

为什么是初中生、是这九个?初中物理有个特点:公式开始出现,但孩子还没建立"公式对应什么画面"。高中的难点在综合应用,初中的难点恰恰在这个"对应"。sin(2θ) 背得出,但 2θ 是什么、为什么是 2 倍角、45° 为什么是顶点,全靠想象。所以我把九个都选在"公式简单、但需要画面才能真懂"的点上:抛体、匀变速、弹簧、单摆、波的叠加、共振、行星、碰撞、双缝。每一个都是课本里"老师画静态图、学生靠脑补"的典型,正好是模拟器最能帮上忙的地方。

九个模拟器,每个都是独立的小世界

每个模拟器是一个完整的 HTML 文件,自己带 Canvas 画图、滑块调参、requestAnimationFrame 动画循环,还有物理积分。九个共享一套深色板岩主题,手机端用 ResizeObserver 盯着容器大小、用 devicePixelRatio 处理高分屏,所以手机上不糊。

每个模拟器里都嵌了一句"点题"和一段可折叠的"详解"。点题是一句话把生活现象和物理挂钩,详解是从生活讲到公式、再给"动手试试"的引导。比如波的叠加那句点题:

两列波叠在一起,峰对峰变高、峰对谷抵消——这就是干涉。

点开详解,能看到降噪耳机、肥皂泡的彩色、两块石头扔水里,都是干涉;再往下是公式 y=A·sin(2π(ft-x/λ))、拍频 f_拍=|f₁-f₂|、驻波 y=2A·sin(kx)·cos(ωt)

单个模拟器的界面结构

九个分四组,我挨个说做了什么、卡在哪、公式是什么。

运动学两个。 抛体运动,拖角度滑块,能看到射程 R=v₀²sin(2θ)/g 先涨后落,45° 是顶点;拖初速度,轨迹整体放大。匀变速直线运动,重点是 v-t 图上那块面积就是位移 x=x₀+v₀t+½at²,预设了自由落体、刹车两种场景,孩子能直观看到"踩油门加速、踩刹车减速,速度图上的面积就是走过的路"。

振动与波四个,最费劲。 弹簧振子用了 RK4 积分(m·x''=-k·x-b·x'),还做了子步长保证稳定,能量柱实时显示守恒,周期靠过零点实测去对 T=2π√(m/k)。拖重物周期变长,换硬弹簧周期变短,实测值和公式对得上才算过关。这里有个细节:显式欧拉法积分弹簧会越积越发散,能量不守恒,画面上小球振幅越来越大;换成 RK4 加子步长(每帧切 8 小步),能量曲线才平。单摆同理,T=2π√(L/g),但大角度要加修正 T≈T₀(1+θ₀²/16),所以摆角拉到 60° 时实测周期会比小角公式长一点。这个偏差本身就是教学点,告诉孩子"公式有适用范围,小角才准"。波的叠加做了相长、相消、拍频、驻波四个预设,勾上"拍频包络"能清楚看到幅度一胀一缩,胀缩频率正好等于两波频率差。共振画了幅频曲线 A=F₀/√((k-mω²)²+(bω)²),推力频率扫过固有频率 f₀=½π√(k/m) 时振幅飙升,预设里还放了塔科马大桥的案例。1940 年那座桥就是被风以接近固有频率的涡旋激振,振幅越积越大最后垮了。

力学两个。 行星轨道靠初速度区分圆、椭圆、抛物、双曲:v_c=√(GM/r) 是圆轨道,v_e=√(2GM/r) 是逃逸,超过就一去不返;还能验证开普勒第三定律 T²/a³=4π²/GM。动量碰撞做了一维对撞,动量 m₁u₁+m₂u₂=m₁v₁+m₂v₂ 永远守恒,恢复系数 e=(v₂-v₁)/(u₁-u₂) 可调,从完全弹性到完全粘在一起,能量损失实时显示。"弹得越脆能量越省,粘在一起能量最亏"。

光学一个。 双缝干涉,最反直觉的那个:光像波会干涉,但粒子一个一个打、打到屏幕上累积,最后也出条纹,这就是波粒二象性。条纹间距 Δy=λL/d,强度分布是 sinc²·cos² 的乘积。这个模拟器我特意做了"逐个粒子累积"的模式,让条纹是一颗一颗长出来的。

真正的难点:九个文件怎么塞进一个文件

九个模拟器单独跑都没问题,但用户要的是"一个文件双击即用"。最直觉的做法是把九份 HTML 拼一起。一试就崩。九套 CSS 变量同名(都叫 --accent--panel),九个 IIFE 各自启动 requestAnimationFrame 循环,互相覆盖、九个动画一起跑、CPU 飙满。

我最后用的办法是:把每个模拟器原样塞进一个 <script type="text/html"> 模板标签里。type="text/html" 这个类型浏览器不认识,所以它不解析、不执行,只把内容当纯文本存着。点哪个卡片,就取出对应模板的文本,用 iframe 的 srcdoc 属性注入进去运行。

单文件合并架构

这个方案的好处很实在。iframe 是独立文档,九套 CSS、九个 JS 各管各的,谁也不污染谁;同一时刻只有当前那个模拟器在跑动画循环,返回首页就清空 srcdoc、停掉循环,手机上省电;九个模拟器的源文件一行都不用改,构建脚本只做读取、塞进模板、拼接三件事。

为什么不用别的方案?我对比过几个。把九个模拟器的 JS 全塞进一个全局作用域、靠命名空间隔开,九套代码挤一起,一个变量名撞了全崩,维护噩梦。用 Shadow DOM 隔离的话,CSS 能隔,但 JS 的 requestAnimationFrame 还是共享一个事件循环,九个动画一起跑的问题没解决。每个模拟器单独一个 HTML 文件、用链接跳转倒也行,但那就不叫"一个文件"了,分发还是九个文件。

iframe srcdoc 是唯一同时解决"隔离、省电、源文件不改、单文件分发"四件事的方案。代价是 iframe 内外跨域通信麻烦,但这个项目里子页面不需要和外壳通信,各跑各的,代价为零。

那个让"点卡片没反应"的坑

合并完,双击打开,点卡片,什么都没发生。

这种 bug 最难受,因为没报错,就是没反应。我用 Chrome 无头模式跑:chrome --headless=new --dump-dom --virtual-time-budget=2000 文件路径,把渲染后的 DOM dump 出来,发现首页的卡片按钮上根本没绑 click 事件。往上追,是那个负责绑事件的 IIFE 没执行。再往上,是主 <script> 块提前关闭了,后面的代码全没解析。

那个让点卡片没反应的坑

根因藏在一行看起来人畜无害的代码里。模板内容里有 </script>(每个模拟器自己有 script 标签),直接放进去会提前结束外层 script,所以我用占位符 __SCR_END__ 替换掉它,注入 iframe 时再还原。还原那行 JS 是这么写的:

var doc = tpl.textContent.replace(/__SCR_END__/g, '</script>');

问题在这:HTML 解析器不懂 JS 字符串字面量。它扫到一个 <script> 块,就一路往后找字面的 </script> 来结束这个块,它不管这个 </script> 是在正则里、在字符串里、还是在注释里。这行 JS 里的 '</script>' 是个字符串,对 JS 来说没毛病,但对 HTML 解析器来说,它就是"主 script 块到此结束"。于是主脚本提前关闭,IIFE 没解析,点击没反应。

修法就一个字符:把 '</script>' 写成 '<\/script>'。反斜杠在这里不是为了 JS 转义(JS 里 \/ 就是 /),而是为了骗过 HTML 解析器。它扫到 <\/ 时,因为不是 </,不认为是结束标签,继续往后;等 JS 真正运行时,<\/script></script> 完全等价。

更稳妥的写法是拆开字符串拼接,把结束标签拆成两段用加号连回去,让这个结束标签的字面量永远不在源码里连在一起。两种都行,我选了反斜杠,因为只多一个字符、可读性更好。

这是个挺典型的"两层语言叠在一起"的坑。HTML 解析永远在 JS 之前,script 块里只要出现字面的结束标签字串(哪怕在字符串里、在正则里、在注释里),块就提前结束。这是 HTML 规范定的行为,不是 bug,但坑过的人应该不少。同样的坑在 PHP 里嵌 JS、在模板引擎里输出 JS 时都会出现。只要外层语言负责把内层代码当文本读,又拿这个结束标签当收尾标记,就躲不开。

用到的工具和能复用的部分

整个过程用的工具其实不多。Claude Code 本身干了大头:读九个源文件、写构建脚本、跑无头验证、迭代修 bug。Chrome 无头模式用来验证 DOM 和动画,--dump-dom 看结构,--virtual-time-budget 让虚拟时间推进、动画跑几秒再 dump。

有个坑值得记一笔:file:// 协议下,iframe 的 contentDocument 是跨域的,外部脚本读不到 iframe 里的内容。所以验证"模拟器有没有真的跑起来"不能从外面读 iframe,得在页面里自己探测:往 iframe 里塞一段自报状态的代码,让它把结果写回父页面。这种"绕过跨域限制做自探测"的思路,做离线工具时会反复用到。

这套流程是能复制的。任何"多个独立页面 → 单个文件分发"的需求都能套这个模板:教学课件把十个实验塞一个文件、产品 Demo 把几个界面打包发客户、离线工具集不依赖服务器。核心就三步,每个子页面塞进 <script type="text/html"> 模板、点击时用 iframe srcdoc 注入、</script> 用占位符加转义处理。

成品长什么样,能拿来干嘛

最后产出三个东西:物理模拟器.html,约 192 KB,单文件,双击即用,离线,手机自适应;九个独立的模拟器源文件,想单独用也行;一个构建脚本,改了源文件跑一下就重新合并。

用法很朴素:拷到 U 盘、微信发文件、传手机浏览器,都直接开。还能用 URL 记忆,物理模拟器.html#pendulum 直接跳到单摆,刷新也留在原地。

适合谁用呢?物理老师上课拖一下滑块演示,比画图强;家长陪初中生孩子玩,比刷题有意思;自学的人验证公式,把"背的"变成"看到的";做科普短视频的,录屏就是素材。

我做这事的体会是:物理直觉是动手养出来的,不是背出来的。AI 帮你把"从公式到能动手调的模拟器"这条路一次性跑通,省下的是写前端、调动画、处理高分屏那些杂活,留下的是真正该花时间的部分:把物理讲清楚。

你学物理的时候,哪个概念最缺"手感"?评论区说说,下一批模拟器我照着做。有用的话点个在看,让更多想给孩子做教具的人看到。


标签: Claude Code 物理模拟器 Canvas iframe srcdoc 前端工程 教育工具 RK4 积分 单文件分发

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐