【OpenHarmony/HarmonyOs 】Canvas 实现光的干涉:从波长滑块到动态条纹可视化

本文基于我的 OpenHarmony/HarmonyOS 项目「物理视界 PhysicsVision」整理。项目中的「光的干涉」模型使用 ArkUI Canvas 绘制光源、双缝、波阵面、屏幕和干涉条纹,并通过滑块实时改变波长、缝间距和屏距。
这一篇单独拆解这个模型:如何把物理公式变成可交互的视觉体验。🌈

一、为什么要用 Canvas 做物理实验?

物理学习中有很多抽象概念,例如:

  • 波长;
  • 相位差;
  • 光程差;
  • 干涉加强和减弱;
  • 条纹间距。

如果只用文字解释,学生很难形成直观印象。
而 Canvas 的价值就在于:可以把公式、参数和图像放在同一个交互循环里。

在「光的干涉」页面中,用户拖动滑块后会同时看到:

  • 光源颜色变化;
  • 干涉条纹颜色变化;
  • 条纹间距变化;
  • 数值面板实时刷新;
  • 公式 Δy = λL/d 对应结果变化。

这比静态图片更适合学习。

二、Canvas 初始化

页面中先创建 Canvas 渲染上下文:

private settings: RenderingContextSettings = new RenderingContextSettings(true)
private ctx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
private cw: number = 360
private ch: number = 360
private canvasReady: boolean = false

在 UI 中使用 Canvas 组件:

Canvas(this.ctx)
  .width('100%')
  .height(340)
  .backgroundColor('#1B2838')
  .border({ width: 2, color: $r('app.color.border_strong') })
  .borderRadius(20)
  .onReady(() => {
    this.canvasReady = true
    this.drawScene()
  })

onReady 很重要。只有 Canvas 准备好之后,才能安全调用绘制逻辑。

三、核心参数:波长、缝间距、屏距

光的干涉页面定义了三个核心状态:

@State slitSpacing: number = 0.5
@State wavelength: number = 550
@State screenDist: number = 2

分别对应:

  • slitSpacing:双缝间距,单位 mm;
  • wavelength:波长,单位 nm;
  • screenDist:屏幕距离,单位 m。

这三个变量共同决定条纹间距。

四、物理公式:条纹间距计算

双缝干涉条纹间距公式为:

Δy = λL / d

项目中的计算方法如下:

getFringeSpacing(): number {
  return (this.wavelength * 1e-9 * this.screenDist) / (this.slitSpacing * 1e-3) * 1000
}

这里做了单位换算:

  • wavelength * 1e-9:nm 转 m;
  • slitSpacing * 1e-3:mm 转 m;
  • 最后 * 1000:m 转 mm。

这个函数不只是用于显示数据,也会影响 Canvas 中条纹的视觉表现。

五、波长到颜色的映射

为了让光学实验更直观,项目把波长映射为颜色:

getColorFromWavelength(): string {
  let wl: number = this.wavelength
  if (wl < 440) return '#8B00FF'
  if (wl < 490) return '#0000FF'
  if (wl < 510) return '#00BFFF'
  if (wl < 550) return '#00FF00'
  if (wl < 590) return '#FFFF00'
  if (wl < 630) return '#FF8C00'
  return '#FF0000'
}

这不是严格的光谱渲染算法,但足够用于教学展示。
学生拖动波长滑块时,能直观看到颜色从紫、蓝、青、绿、黄、橙到红的变化。

六、绘制光源:颜色和光晕

drawScene 中,先绘制深色背景:

ctx.clearRect(0, 0, w, h)
ctx.fillStyle = '#1B2838'
ctx.fillRect(0, 0, w, h)

再绘制光源:

ctx.fillStyle = this.getColorFromWavelength()
ctx.beginPath()
ctx.arc(sourceX, cy, 10, 0, Math.PI * 2)
ctx.fill()

ctx.shadowColor = this.getColorFromWavelength()
ctx.shadowBlur = 15
ctx.fill()
ctx.shadowBlur = 0

shadowBlur 让光源产生发光感。
这类视觉细节虽然不是物理公式的一部分,但能增强沉浸感。

七、绘制双缝和光波

双缝前的挡板用矩形表示:

ctx.fillStyle = this.dimColor()
ctx.fillRect(slitX - 3, 0, 6, cy - 20)
ctx.fillRect(slitX - 3, cy - 10, 6, 20)
ctx.fillRect(slitX - 3, cy + 10, 6, h - cy - 10)

从双缝发出的波用圆弧表示:

ctx.globalAlpha = 0.15
for (let i = 1; i <= 8; i++) {
  let r: number = i * 25
  ctx.strokeStyle = this.getColorFromWavelength()
  ctx.lineWidth = 1.5
  ctx.beginPath()
  ctx.arc(slitX, slitY1, r, -Math.PI / 3, Math.PI / 3)
  ctx.stroke()
  ctx.beginPath()
  ctx.arc(slitX, slitY2, r, -Math.PI / 3, Math.PI / 3)
  ctx.stroke()
}
ctx.globalAlpha = 1

这里用半透明圆弧表示波阵面,非常适合教学演示。

八、绘制干涉条纹

屏幕上的条纹根据强度计算透明度:

for (let y = -15; y <= 15; y++) {
  let py: number = cy + y * fringeScale
  if (py < 25 || py > h - 25) continue

  let pathDiff: number = y * this.slitSpacing / this.screenDist
  let phase: number = 2 * Math.PI * pathDiff / (this.wavelength * 0.001)
  let intensity: number = Math.cos(phase / 2) * Math.cos(phase / 2)

  ctx.fillStyle = color
  ctx.globalAlpha = intensity * 0.9
  ctx.fillRect(screenX - 8, py - fringeScale / 2.5, 16, fringeScale / 1.5)
}
ctx.globalAlpha = 1

这里的思路是:

  • 用位置估算光程差;
  • 用相位差估算干涉强度;
  • 用透明度表示亮暗变化。

最终效果是:亮纹更亮,暗纹更淡。

九、滑块交互:参数变化后立即重绘

波长滑块:

Slider({ value: this.wavelength, min: 380, max: 700, step: 10 })
  .trackColor($r('app.color.slider_track'))
  .selectedColor(this.getColorFromWavelength())
  .onChange((v: number) => {
    this.wavelength = v
    if (this.canvasReady) this.drawScene()
  })

缝间距滑块:

Slider({ value: this.slitSpacing, min: 0.1, max: 1.0, step: 0.05 })
  .selectedColor('#1A73E8')
  .onChange((v: number) => {
    this.slitSpacing = v
    if (this.canvasReady) this.drawScene()
  })

屏距滑块:

Slider({ value: this.screenDist, min: 0.5, max: 5, step: 0.1 })
  .selectedColor('#1A73E8')
  .onChange((v: number) => {
    this.screenDist = v
    if (this.canvasReady) this.drawScene()
  })

每次参数变化都触发 drawScene,所以用户获得的是即时反馈。

十、数据面板:让图像和公式对应起来

页面下方展示干涉数据:

Text(`${this.getFringeSpacing().toFixed(2)} mm`)
  .fontSize(17)
  .fontWeight(FontWeight.Bolder)
  .fontColor('#1A73E8')

Text('Δy = λL/d')
  .fontSize(13)
  .fontWeight(FontWeight.Bold)
  .backgroundColor($r('app.color.bg_tag'))

这一步非常重要。
如果只有动画,学生可能只是觉得好看;加上公式和数值,才能真正把现象和知识连接起来。

总结

这个光的干涉模型展示了 ArkUI Canvas 在教育应用中的价值。
它不只是画图,而是把“参数输入、公式计算、物理图像、实时反馈”组合成一个完整学习场景。

对 OpenHarmony/HarmonyOS 开发者来说,这类页面非常适合做课程演示、实验模拟、科普应用和互动教学工具。
当学生拖动一个滑块就能看到光条纹变化时,物理公式就不再只是书上的符号了。🌈

img

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐