同一块 240Hz PWM 屏幕,1/2000 快门能拍到 4 条暗带。
把快门拖到 1/60,暗带变成 0 条,频闪指数还是 50.0%。
黑纹不是灯坏了,是相机逐行读出撞上了 PWM 的亮灭周期。

在线案例与源码:gitcode.com/OPCTeam/dem… 成品是一个 index.html,双击就能打开,不连网,不装依赖。
一、先看结果:240Hz 屏幕,快门一换暗带就消失
页面默认 240Hz、50% 占空比、1/2000 快门。
相机取景器里是 4 条横向暗带。
右侧指标卡同时给出频闪指数 50.0%、暗带 4 条。
我把快门从 1/2000 拖到 1/60,暗带归零。
频闪指数没有变,仍然是 50.0%。
这说明光没有变,变的是每一行像素的积分窗口。
手机拍屏幕出现条纹,经常被解释成“快门太快”。
这句话只对了一半。
快门快,单行曝光短,容易撞上亮灭瞬间。
快门慢到正好覆盖整数个周期,条纹也会消失,但频闪并没有消失。
这个页面把两种情况都摆在同一屏,让你自己拖出来。
二、为什么值得看:条纹是卷帘快门和 PWM 撞在一起
搜“手机拍屏幕 条纹”,出来最多的是手机广告和“快门调慢一点”的结论。
很少有人把光源波形、手机面板和相机取景器放在同一屏。
更少有人告诉你,暗带条数不等于频闪频率。
240Hz 的光源,在 15ms 读出里只跨 3.6 个周期。
所以画面里是 4 条暗带,不是 240 条。
这个页面就是用来拆开这层关系的。
它不判断某块屏幕伤不伤眼。
它只回答一件事:同样的光,为什么换个快门,相机看到的结果完全不同。
三、模型怎么跑:420 行传感器,逐行算积分
整个页面是一个 1600×1000 的单文件 index.html。
没有 CDN,没有外部字体,没有图片依赖。
绘图全部走 Canvas。
光源用 lightAt(t) 生成矩形波。
给定频率和占空比,任意时刻的亮度只有 0 或 1。
相机的卷帘读出用 sampleRowProfile 模拟。
它把 15ms 读出时间切成 420 行。
每一行从自己的起始时刻开始,积分一个快门窗口。
窗口内亮的时间占比,就是这一行的平均亮度。
暗带用 darkBandSegments 统计。
先取整列亮度的最小值,再加 36% 的峰谷差作为阈值。
低于阈值的连续行算一条暗带。
频闪指数用 flickerIndex 计算。
采样 6 个周期、4096 个点。
高于平均值的面积除以总面积,就是频闪指数。
混叠判断在 aliasInfo 里。
快门时间除以光源周期,如果接近整数倍,就标记为混叠。
页面把这条判断显示在指标区,避免你看图得出反结论。
四、实测:15ms 读出跨 3.6 个周期,所以是 4 条不是 240 条
我把几组常用参数跑了一遍,数据如下。
| 光源 | 快门 | 快门除以周期 | 暗带 | 频闪指数 |
|---|---|---|---|---|
| 240Hz / 50% | 1/2000 | 0.120 T | 4 条 | 50.0% |
| 240Hz / 50% | 1/250 | 0.960 T | 4 条 | 50.0% |
| 240Hz / 50% | 1/125 | 1.920 T | 4 条 | 50.0% |
| 240Hz / 50% | 1/60 | 4.000 T | 0 条 | 50.0% |
| 480Hz / 50% | 1/2000 | 0.240 T | 7 条 | 50.0% |
| 480Hz / 50% | 1/250 | 1.920 T | 8 条 | 50.0% |
| 1440Hz / 50% | 1/2000 | 0.720 T | 22 条 | 50.0% |
| DC 恒流 | 任意 | 无周期 | 0 条 | 0.0% |
240Hz 的周期是 4.167ms。
15ms 读出除以 4.167ms,等于 3.6。
所以画面里出现 4 条暗带,边缘那条只覆盖了半个周期。
480Hz 的周期是 2.083ms。
15ms 跨 7.2 个周期,暗带落在 7 到 8 条之间。
换快门相位,边缘那条会多切出一段。
1440Hz 的周期是 0.694ms。
15ms 跨 21.6 个周期,暗带变成 22 条。
这就是为什么高频 PWM 的条纹更密。
不是频率越高越容易拍出来,是同样读出时间里周期更多。

我点了“跑 5 组对照”。
页面计时 0.60ms,五组结果是 4、7、22、4、0 条。
自检面板是 8/8 PASS,用时 0.40ms。
其中一条自检专门测占空比。
10% 占空比的频闪指数是 0.9001。
90% 占空比的频闪指数是 0.1003。
这组数字有点反直觉。
占空比越低,灯越暗,频闪指数反而越高。
因为频闪指数看的是波形不对称程度,不是亮度。
10% 占空比时,只有 10% 的时间是亮的,平均值被拉得很低,亮的部分离平均值很远。
五、验证与踩坑:1/60 快门把 4 条暗带抹成 0
这是我最想让你看的一张图。
光源仍然是 240Hz、50% 占空比。
快门从 1/2000 拖到 1/60。
1/60 是 16.667ms,正好等于 4 个 PWM 周期。
每一行都完整覆盖 4 个亮灭循环,平均亮度都是 50%。
于是暗带变成 0 条。
指标区的频闪指数还是 50.0%。
页面弹出黄色警告:“读数可能被混叠欺骗”。
下面一行写得很具体:“快门 1/60 ≈ 4 × 周期 4.167 ms”。

如果你只看相机取景器,会得出“这盏灯没有频闪”的结论。
这和 50.0% 的频闪指数正好相反。
我把这个状态当作失败路径截图保留下来。
复现方法很简单。
保持 240Hz 和 50% 占空比,只把快门拉到最左边的 1/60。
看暗带是否归零,警告是否出现。
自检里还有一条 1/120 快门整数倍混叠。
它算出的比值是 2.0000,判定倍数是 2。
但 UI 的快门档位里没有 1/120。
最接近的是 1/125,比值 1.920 T。
1/125 不会触发混叠,暗带仍然是 4 条。
所以想看到可见的混叠,用 1/60 最直接。
这也是页面自检和 UI 档位之间的一处边界。
自检测的是公式,不是每一个 UI 档位都能复现。
六、它不是真实相机测试
这个页面没有接相机,也没有拍真实屏幕。
所有亮度曲线都来自矩形波模型。
真实 PWM 有上升沿、下降沿和过冲。
真实相机有噪声、去马赛克、自动曝光和抗频闪算法。
有些手机会自动把快门调到 1/100 或 1/120 来避开频闪。
有些屏幕用 DC 调光,有些用高频 PWM,还有些混着两种方案。
页面里的 15ms 读出、420 行传感器,都是可调的教学参数。
真实手机的读出时间、行数和曝光策略各不相同。
所以它适合用来理解机制。
别拿它给某块屏幕下“伤眼”或“护眼”的结论。
七、准备环境:码道 Web 与本地复现
浏览器打开码道 Web 版:devcloud.cn-north-4.huaweicloud.com/chat?source…
码道有三种使用方式:WebUI(浏览器对话)、TUI(终端命令行)和桌面 IDE(IDE 插件)。本文用 WebUI 版演示。
下载 index.html 后,可以直接双击打开。
也可以在当前目录起一个静态服务:python -m http.server 8000,再打开 http://localhost:8000/index.html。
按这个顺序点,能复现本文的数字:
- 保持 240Hz、50% 占空比、1/2000 快门,看暗带 4 条。
- 点“跑 5 组对照”,看结果 4、7、22、4、0 条。
- 把快门拉到 1/60,看暗带归零和混叠警告。
- 切到 1440Hz,保持 1/2000,看暗带变成 22 条。
- 切到 DC,看暗带和频闪指数同时归零。
自检面板 8/8 PASS。
失败路径提示原文:读数可能被混叠欺骗。

480Hz 也值得单独看一眼。
同样 15ms 读出,它跨 7.2 个周期。
把快门放到 1/250,暗带是 8 条。

八、直接下载试玩
这个 Demo 已经完整开源,源代码、本文档和全部截图都在公开仓库:
- Demo Park 仓库:gitcode.com/OPCTeam/dem…
- 本案例目录:gitcode.com/OPCTeam/dem…
进入 codearts-pwm-flicker-lab 文件夹,把 index.html 下载到本地,用浏览器打开即可运行。
九、使用码道体会
需求里我把验收条件写死了。
1600×1000、单文件、无外部依赖、8 条断言、5 组对照、混叠警告。
页面把这些都做出来了,而且把自检结果直接渲染在界面上。
我做的事不是替它写循环,而是把“什么算对”写成可执行的数字。
平均亮度要在 0.5000 上下,10% 占空比的频闪指数要接近 0.9,DC 必须是 0 条暗带。
这些条件越具体,验收越省事。
真正需要我自己判断的是失败路径。
自检全绿不代表每个 UI 档位都能触发混叠。
我把快门拖到 1/60,才看到暗带归零和黄色警告。
这也是我建议每个读者亲手点一遍的地方。
十、下一步
还缺三块。
一是真实相机样张对照。
同一块屏幕,用 1/60、1/125、1/2000 各拍一张,和页面预测并排。
二是更真实的光源波形。
加上上升沿、下降沿和幅度调制,看看频闪指数会怎么变。
三是把逐行亮度导出成 CSV。
方便拿真实相机的行亮度曲线做误差对比。
仓库地址在文章开头,拿去改。
附录:复现用需求
下面是我用来生成这个页面的需求,和页面实际实现一致,可以直接复制:
用单文件 index.html 做一个中文的「PWM 频闪实验室」,纯前端、无任何外部依赖和 CDN,用 Canvas 绘图,深色科技风,桌面 1600x1000 好看。
核心功能:模拟手机屏幕的 PWM 调光和相机卷帘快门。控件包括调光方式(DC 恒流、240Hz、480Hz、1440Hz PWM)、占空比 5% 到 95%、快门 1/60 到 1/4000、卷帘读出 5ms 到 30ms。
模型:PWM 光源输出矩形波;相机 420 行逐行读出,每行从不同相位开始积分一个快门窗口,窗口内亮的时间占比就是该行平均亮度;暗带按整列亮度最小值加 36% 峰谷差做阈值统计;频闪指数按高于平均值的面积除以总面积计算,采样 6 个周期、4096 个点;快门时间除以光源周期接近整数倍时标记为混叠。
界面布局:左侧光源控制;中间上方是手机发光面板;中间右侧是相机取景器;下方左侧是实时指标和自检面板;下方中间是 PWM 波形和逐行曝光曲线;下方右侧是 5 组对照表和暗带柱状图。
还要有:一键「跑 5 组对照」,固定比较 240Hz 50%、480Hz 50%、1440Hz 50%、240Hz 10%、DC 五组;一个 8 条断言的自检面板,例如 50% 占空比平均光量 0.5000、10% 占空比频闪指数 0.9、DC 零频闪、1/120 快门整数倍混叠、1/480 半周期安全、15ms 读出跨 3.6 个周期、逐行亮度在 0 到 1 之间、5 组对照矩阵完整,每条显示 PASS 或 FAIL 和实际值;快门下接近周期整数倍时显示中文混叠警告。
交互要顺滑,数字用等宽字体,所有文案中文。完成后打开预览,让我直接看到运行效果。