【OpenHarmony/HarmonyOs 】平抛运动 2D 与 3D 视图:Canvas 动画、轨迹点与视角滑块实践
【OpenHarmony/HarmonyOs 】平抛运动 2D 与 3D 视图:Canvas 动画、轨迹点与视角滑块实践
本文基于我的 OpenHarmony/HarmonyOS 项目「物理视界 PhysicsVision」整理。项目中的「平抛运动」模型不仅有 2D 抛物线轨迹,还提供了 3D 视图、视角滑块、轨迹点记录、速度箭头和播放控制。
这一篇对应“全新视觉与交互体验”和“端侧 AI/端侧计算能力”的方向,重点讲如何用 ArkTS + Canvas 做动态物理模拟。⚽
一、为什么平抛运动适合做成动态模型?
平抛运动是高中物理中的经典模型。它有两个方向:
- 水平方向:匀速直线运动;
- 竖直方向:自由落体运动。
课本上通常给公式:
x = v0t
y = 1/2gt²
但学生真正难理解的是:
为什么水平速度不变,竖直速度却越来越大?轨迹为什么是抛物线?
所以项目把它做成 Canvas 动画,让用户看到小球从平台飞出、轨迹逐渐形成、速度箭头实时变化。
二、核心状态设计
平抛页面中定义了这些状态:
@State v0: number = 10
@State isPlaying: boolean = false
@State time: number = 0
@State posX: number = 0
@State posY: number = 0
@State is3D: boolean = false
@State camAngle: number = 0.6
这些状态分别表示:
- 初速度;
- 是否播放;
- 当前时间;
- 水平位移;
- 竖直下落位移;
- 当前是否为 3D 视图;
- 3D 摄像机角度。
通过这些状态,页面可以同时管理物理计算、动画播放和视角切换。
三、轨迹点结构
项目定义了一个简单接口:
interface ProjPoint {
x: number
y: number
}
并用数组保存轨迹:
private points: ProjPoint[] = []
每一帧计算出当前位置后,把点加入轨迹数组。
这样 Canvas 就能绘制历史路径,而不只是显示当前小球位置。
四、动画播放:16ms 定时刷新
启动动画的方法如下:
startAnimation(): void {
if (this.timerId !== -1) return
this.time = 0
this.posX = 0
this.posY = 0
this.points = []
this.isPlaying = true
this.timerId = setInterval(() => {
this.time += 0.03
this.posX = this.v0 * this.time
this.posY = 0.5 * this.gravity * this.time * this.time
let p: ProjPoint = { x: this.posX, y: this.posY }
this.points.push(p)
if (this.canvasReady) this.redraw()
}, 16)
}
这里的核心就是公式:
this.posX = this.v0 * this.time
this.posY = 0.5 * this.gravity * this.time * this.time
每 16ms 刷新一次,接近 60fps 的动画体验。
五、生命周期清理:避免定时器泄漏
页面退出时清理定时器:
aboutToDisappear(): void {
if (this.timerId !== -1) {
clearInterval(this.timerId)
this.timerId = -1
}
}
这是动画页面必须注意的细节。
如果不清理,用户离开页面后定时器还在跑,会浪费资源,也可能导致状态异常。
六、2D 绘制:平台、网格、轨迹、小球
2D 绘制方法中先画背景、平台和地面:
ctx.clearRect(0, 0, w, h)
ctx.fillStyle = this.canvasBg()
ctx.fillRect(0, 0, w, h)
ctx.fillStyle = '#DFE6E9'
ctx.strokeStyle = this.wireColor()
ctx.lineWidth = 3
ctx.beginPath()
ctx.rect(0, offsetY - 10, offsetX + 10, h - offsetY + 10)
ctx.fill()
ctx.stroke()
再绘制轨迹:
if (this.points.length > 1) {
ctx.strokeStyle = '#4D96FF'
ctx.lineWidth = 2
ctx.setLineDash([4, 4])
ctx.beginPath()
for (let i = 0; i < this.points.length; i++) {
let px = this.points[i].x * scaleF + offsetX
let py = this.points[i].y * scaleF + offsetY
if (i === 0) ctx.moveTo(px, py)
else ctx.lineTo(px, py)
}
ctx.stroke()
ctx.setLineDash([])
}
虚线轨迹让运动路径更清晰。
七、速度箭头:拆分水平和竖直方向
播放时显示水平和竖直速度箭头:
if (this.isPlaying) {
ctx.strokeStyle = '#1A73E8'
ctx.beginPath()
ctx.moveTo(ballX + 18, ballY)
ctx.lineTo(ballX + 45, ballY)
ctx.stroke()
let vyLen = Math.min(this.posY * 2, 50)
if (vyLen > 5) {
ctx.strokeStyle = '#FF6D00'
ctx.beginPath()
ctx.moveTo(ballX, ballY + 18)
ctx.lineTo(ballX, ballY + 18 + vyLen)
ctx.stroke()
}
}
蓝色表示水平速度,橙色表示竖直方向变化。
学生可以直观看到:水平速度保持,竖直速度越来越明显。
八、3D 视图:用投影函数模拟空间感
项目中并没有引入 Three.js,而是用 Canvas 自己做简单 3D 投影:
proj(x3d: number, y3d: number, z3d: number): number[] {
let c = Math.cos(this.camAngle)
let s = Math.sin(this.camAngle)
let rx = x3d * c - z3d * s
let rz = x3d * s + z3d * c + 400
let f = 480
let sc = f / rz
return [this.cw / 2 + rx * sc, this.ch * 0.55 - y3d * sc, sc]
}
这段代码做了三个步骤:
- 根据视角旋转坐标;
- 加上景深距离;
- 通过透视比例映射到屏幕。
虽然是简化 3D,但对教学展示已经很有效。
九、视角滑块:让用户主动观察
3D 模式下显示视角滑块:
Slider({ value: this.camAngle * 100, min: 0, max: 628, step: 5 })
.layoutWeight(1)
.trackColor($r('app.color.slider_track'))
.selectedColor('#4D96FF')
.onChange((v: number) => {
this.camAngle = v / 100
if (this.canvasReady) this.redraw()
})
用户拖动滑块后,3D 场景实时变化。
这让模型从“看动画”变成“操作实验”。
十、总结
平抛运动页面是项目中很典型的“端侧物理模拟”案例。
它没有请求网络,也没有调用外部 AI,而是用本地公式、Canvas 绘制和状态更新完成了动态演示。
这篇文章对应的主题是:全新视觉与交互体验 + 端侧计算能力。
对 CSDN 读者来说,它能展示 OpenHarmony/HarmonyOS 不只适合做表单和列表,也能做有动画、有交互、有物理逻辑的学习应用。🚀

更多推荐



所有评论(0)